VersionCountingBlockStore::forEachBlock() has an integrity check checking that all expected blocks are still existing.
This commit is contained in:
parent
491b277cee
commit
f066b45954
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user