VersionCountingBlockStore::forEachBlock() has an integrity check checking that all expected blocks are still existing.

This commit is contained in:
Sebastian Messmer 2016-06-25 16:36:41 -07:00
parent 491b277cee
commit f066b45954
5 changed files with 96 additions and 2 deletions

View File

@ -1,5 +1,6 @@
#include <fstream> #include <fstream>
#include <cpp-utils/random/Random.h> #include <cpp-utils/random/Random.h>
#include <unordered_set>
#include "KnownBlockVersions.h" #include "KnownBlockVersions.h"
namespace bf = boost::filesystem; namespace bf = boost::filesystem;
@ -205,6 +206,16 @@ bool KnownBlockVersions::blockShouldExist(const Key &key) const {
return found->second != CLIENT_ID_FOR_DELETED_BLOCK; return found->second != CLIENT_ID_FOR_DELETED_BLOCK;
} }
std::unordered_set<Key> KnownBlockVersions::existingBlocks() const {
std::unordered_set<Key> result;
for (const auto &entry : _lastUpdateClientId) {
if (entry.second != CLIENT_ID_FOR_DELETED_BLOCK) {
result.insert(entry.first);
}
}
return result;
}
const bf::path &KnownBlockVersions::path() const { const bf::path &KnownBlockVersions::path() const {
return _stateFilePath; return _stateFilePath;
} }

View File

@ -10,6 +10,7 @@
#include <cpp-utils/data/Deserializer.h> #include <cpp-utils/data/Deserializer.h>
#include <cpp-utils/data/Serializer.h> #include <cpp-utils/data/Serializer.h>
#include <mutex> #include <mutex>
#include <unordered_set>
namespace blockstore { namespace blockstore {
namespace versioncounting { namespace versioncounting {
@ -28,6 +29,7 @@ namespace blockstore {
void markBlockAsDeleted(const Key &key); void markBlockAsDeleted(const Key &key);
bool blockShouldExist(const Key &key) const; bool blockShouldExist(const Key &key) const;
std::unordered_set<Key> existingBlocks() const;
uint64_t getBlockVersion(uint32_t clientId, const Key &key) const; uint64_t getBlockVersion(uint32_t clientId, const Key &key) const;

View File

@ -1,3 +1,4 @@
#include <unordered_set>
#include "VersionCountingBlockStore.h" #include "VersionCountingBlockStore.h"
#include "VersionCountingBlock.h" #include "VersionCountingBlock.h"
@ -78,9 +79,24 @@ namespace blockstore {
} }
void VersionCountingBlockStore::forEachBlock(std::function<void (const Key &)> callback) const { void VersionCountingBlockStore::forEachBlock(std::function<void (const Key &)> callback) const {
if (!_missingBlockIsIntegrityViolation) {
return _baseBlockStore->forEachBlock(callback); return _baseBlockStore->forEachBlock(callback);
} }
std::unordered_set<blockstore::Key> existingBlocks = _knownBlockVersions.existingBlocks();
_baseBlockStore->forEachBlock([&existingBlocks, callback] (const Key &key) {
callback(key);
auto found = existingBlocks.find(key);
if (found != existingBlocks.end()) {
existingBlocks.erase(found);
}
});
if (!existingBlocks.empty()) {
throw IntegrityViolationError("A block that should have existed wasn't found.");
}
}
#ifndef CRYFS_NO_COMPATIBILITY #ifndef CRYFS_NO_COMPATIBILITY
void VersionCountingBlockStore::migrateFromBlockstoreWithoutVersionNumbers(BlockStore *baseBlockStore, const bf::path &integrityFilePath) { void VersionCountingBlockStore::migrateFromBlockstoreWithoutVersionNumbers(BlockStore *baseBlockStore, const bf::path &integrityFilePath) {
std::cout << "Migrating file system for integrity features. This can take a while..." << std::flush; std::cout << "Migrating file system for integrity features. This can take a while..." << std::flush;

View File

@ -5,7 +5,9 @@
using blockstore::versioncounting::KnownBlockVersions; using blockstore::versioncounting::KnownBlockVersions;
using blockstore::versioncounting::VersionCountingBlock; using blockstore::versioncounting::VersionCountingBlock;
using blockstore::Key;
using cpputils::TempFile; using cpputils::TempFile;
using std::unordered_set;
class KnownBlockVersionsTest : public ::testing::Test { class KnownBlockVersionsTest : public ::testing::Test {
public: public:
@ -340,3 +342,39 @@ TEST_F(KnownBlockVersionsTest, path) {
KnownBlockVersions obj(stateFile.path()); KnownBlockVersions obj(stateFile.path());
EXPECT_EQ(stateFile.path(), obj.path()); EXPECT_EQ(stateFile.path(), obj.path());
} }
TEST_F(KnownBlockVersionsTest, existingBlocks_empty) {
EXPECT_EQ(unordered_set<Key>({}), testobj.existingBlocks());
}
TEST_F(KnownBlockVersionsTest, existingBlocks_oneentry) {
setVersion(&testobj, clientId, key, 5);
EXPECT_EQ(unordered_set<Key>({key}), testobj.existingBlocks());
}
TEST_F(KnownBlockVersionsTest, existingBlocks_twoentries) {
setVersion(&testobj, clientId, key, 5);
setVersion(&testobj, clientId2, key2, 5);
EXPECT_EQ(unordered_set<Key>({key, key2}), testobj.existingBlocks());
}
TEST_F(KnownBlockVersionsTest, existingBlocks_twoentries_sameKey) {
setVersion(&testobj, clientId, key, 5);
setVersion(&testobj, clientId2, key, 5);
EXPECT_EQ(unordered_set<Key>({key}), testobj.existingBlocks());
}
TEST_F(KnownBlockVersionsTest, existingBlocks_deletedEntry) {
setVersion(&testobj, clientId, key, 5);
setVersion(&testobj, clientId2, key2, 5);
testobj.markBlockAsDeleted(key2);
EXPECT_EQ(unordered_set<Key>({key}), testobj.existingBlocks());
}
TEST_F(KnownBlockVersionsTest, existingBlocks_deletedEntries) {
setVersion(&testobj, clientId, key, 5);
setVersion(&testobj, clientId2, key2, 5);
testobj.markBlockAsDeleted(key);
testobj.markBlockAsDeleted(key2);
EXPECT_EQ(unordered_set<Key>({}), testobj.existingBlocks());
}

View File

@ -212,6 +212,33 @@ TEST_F(VersionCountingBlockStoreTest, DeletionPrevention_DoesntAllowDeletingBloc
); );
} }
// Check that in a multi-client scenario, missing blocks are not integrity errors, because another client might have deleted them.
TEST_F(VersionCountingBlockStoreTest, DeletionPrevention_InForEachBlock_AllowsDeletingBlocksWhenDeactivated) {
FakeBlockStore *baseBlockStore;
unique_ptr<VersionCountingBlockStore> blockStore;
std::tie(baseBlockStore, blockStore) = makeBlockStoreWithoutDeletionPrevention();
auto key = blockStore->create(Data(0))->key();
baseBlockStore->remove(baseBlockStore->load(key).value());
int count = 0;
blockStore->forEachBlock([&count] (const blockstore::Key &) {
++count;
});
EXPECT_EQ(0, count);
}
// Check that in a single-client scenario, missing blocks are integrity errors.
TEST_F(VersionCountingBlockStoreTest, DeletionPrevention_InForEachBlock_DoesntAllowDeletingBlocksWhenActivated) {
FakeBlockStore *baseBlockStore;
unique_ptr<VersionCountingBlockStore> blockStore;
std::tie(baseBlockStore, blockStore) = makeBlockStoreWithDeletionPrevention();
auto key = blockStore->create(Data(0))->key();
baseBlockStore->remove(baseBlockStore->load(key).value());
EXPECT_THROW(
blockStore->forEachBlock([] (const blockstore::Key &) {}),
IntegrityViolationError
);
}
// TODO Test more integrity cases: // TODO Test more integrity cases:
// - RollbackPrevention_DoesntAllowReintroducingDeletedBlocks with different client id (i.e. trying to re-introduce the newest block of a different client) // - RollbackPrevention_DoesntAllowReintroducingDeletedBlocks with different client id (i.e. trying to re-introduce the newest block of a different client)
// - RollbackPrevention_AllowsReintroducingDeletedBlocksWithNewVersionNumber with different client id // - RollbackPrevention_AllowsReintroducingDeletedBlocksWithNewVersionNumber with different client id