Updated hpfs hash verification with file mode checks. (#257)

* Added file mode to hpfs responses.

* Parse file mode on hpfs response read.

* Apply file/dir mode.

* Fixed mode apply logic.

* Fixed code review comments.

* Additional fix.
This commit is contained in:
Ravin Perera
2021-02-24 11:13:28 +05:30
committed by GitHub
parent 363116fc2a
commit 5b56d9c1b3
13 changed files with 201 additions and 80 deletions

View File

@@ -266,33 +266,31 @@ namespace hpfs
std::unordered_map<std::string, p2p::hpfs_fs_hash_entry> peer_fs_entry_map;
p2pmsg::flatbuf_hpfsfshashentry_to_hpfsfshashentry(peer_fs_entry_map, fs_resp.entries());
// Commented for now. Need to change the way the hash is calculated once the flatbuffer re-architecture finishes.
// Validate received fs data against the hash.
// if (!validate_fs_entry_hash(vpath, hash, peer_fs_entry_map))
// {
// LOG_INFO << "Hpfs " << name << " sync: Skipping hpfs response due to fs entry hash mismatch.";
// continue;
// }
if (!validate_fs_entry_hash(vpath, hash, fs_resp.dir_mode(), peer_fs_entry_map))
{
LOG_INFO << "Hpfs " << name << " sync: Skipping hpfs response due to fs entry hash mismatch.";
continue;
}
handle_fs_entry_response(vpath, peer_fs_entry_map);
handle_fs_entry_response(vpath, fs_resp.dir_mode(), peer_fs_entry_map);
}
else if (msg_type == p2pmsg::HpfsResponse_HpfsFileHashMapResponse)
{
const p2pmsg::HpfsFileHashMapResponse &file_resp = *resp_msg.content_as_HpfsFileHashMapResponse();
// File block hashes we received from the peer.
const util::h32 *peer_hashes = reinterpret_cast<const util::h32 *>(file_resp.hash_map()->data());
const size_t peer_hash_count = file_resp.hash_map()->size() / sizeof(util::h32);
const util::h32 *block_hashes = reinterpret_cast<const util::h32 *>(file_resp.hash_map()->data());
const size_t block_hash_count = file_resp.hash_map()->size() / sizeof(util::h32);
// Commented for now. Need to change the way the hash is calculated once the flatbuffer re-architecture finishes.
// Validate received hashmap against the hash.
// if (!validate_file_hashmap_hash(vpath, hash, peer_hashes, peer_hash_count))
// {
// LOG_INFO << "Hpfs " << name << " sync: Skipping hpfs response due to file hashmap hash mismatch.";
// continue;
// }
if (!validate_file_hashmap_hash(vpath, hash, file_resp.file_mode(), block_hashes, block_hash_count))
{
LOG_INFO << "Hpfs " << name << " sync: Skipping hpfs response due to file hashmap hash mismatch.";
continue;
}
handle_file_hashmap_response(vpath, peer_hashes, peer_hash_count, file_resp.file_length());
handle_file_hashmap_response(vpath, file_resp.file_mode(), block_hashes, block_hash_count, file_resp.file_length());
}
else if (msg_type == p2pmsg::HpfsResponse_HpfsBlockResponse)
{
@@ -302,13 +300,12 @@ namespace hpfs
const uint32_t block_id = block_resp.block_id();
std::string_view buf = msg::fbuf::flatbuf_bytes_to_sv(block_resp.data());
// Commented for now. Need to change the way the hash is calculated once the flatbuffer re-architecture finishes.
// Validate received block data against the hash.
// if (!validate_file_block_hash(hash, block_id, buf))
// {
// LOG_INFO << "Hpfs " << name << " sync: Skipping hpfs response due to file block hash mismatch.";
// continue;
// }
if (!validate_file_block_hash(hash, block_id, buf))
{
LOG_INFO << "Hpfs " << name << " sync: Skipping hpfs response due to file block hash mismatch.";
continue;
}
handle_file_block_response(vpath, block_id, buf);
}
@@ -394,18 +391,24 @@ namespace hpfs
* Vadidated the received hash against the received fs entry map.
* @param vpath Virtual path of the fs.
* @param hash Received hash.
* @param dir_mode Metadata 'mode' of the directory containing the fs entries.
* @param fs_entry_map Received fs entry map.
* @returns true if hash is valid, otherwise false.
*/
bool hpfs_sync::validate_fs_entry_hash(std::string_view vpath, std::string_view hash, const std::unordered_map<std::string, p2p::hpfs_fs_hash_entry> &fs_entry_map)
bool hpfs_sync::validate_fs_entry_hash(std::string_view vpath, std::string_view hash, const mode_t dir_mode,
const std::unordered_map<std::string, p2p::hpfs_fs_hash_entry> &fs_entry_map)
{
util::h32 content_hash;
const std::string vpath_name = util::get_name(vpath);
// Initilal hash is vpath hash.
// Initilal hash is vpath hash + mode hash.
content_hash = crypto::get_hash(vpath_name);
uint8_t mode_bytes[4];
util::uint32_to_bytes(mode_bytes, dir_mode);
content_hash ^= crypto::get_hash(mode_bytes, sizeof(mode_bytes));
// Then XOR the file hashes to the initial hash.
for (const auto &[name, fs_entry] : fs_entry_map)
{
@@ -419,19 +422,25 @@ namespace hpfs
* Vadidated the received hash against the received file hash map.
* @param vpath Virtual path of the file.
* @param hash Received hash.
* @param file_mode Metadata 'mode' of the file.
* @param hashes Received block hashes.
* @param hash_count Size of the hash list.
* @returns true if hash is valid, otherwise false.
*/
bool hpfs_sync::validate_file_hashmap_hash(std::string_view vpath, std::string_view hash, const util::h32 *hashes, const size_t hash_count)
bool hpfs_sync::validate_file_hashmap_hash(std::string_view vpath, std::string_view hash, const mode_t file_mode,
const util::h32 *hashes, const size_t hash_count)
{
util::h32 content_hash = util::h32_empty;
const std::string vpath_name = util::get_name(vpath);
// Initilal hash is vpath hash.
// Initilal hash is vpath hash + mode hash.
content_hash = crypto::get_hash(vpath_name);
uint8_t mode_bytes[4];
util::uint32_to_bytes(mode_bytes, file_mode);
content_hash ^= crypto::get_hash(mode_bytes, sizeof(mode_bytes));
// Then XOR the block hashes to the initial hash.
for (int32_t block_id = 0; block_id < hash_count; block_id++)
{
@@ -452,7 +461,9 @@ namespace hpfs
{
// Calculate block offset of this block.
const off_t block_offset = block_id * hpfs::BLOCK_SIZE;
std::string_view offset = std::string_view(reinterpret_cast<const char *>(&block_offset), sizeof(off_t));
uint8_t bytes[8];
util::uint64_to_bytes(bytes, block_offset);
std::string_view offset = std::string_view(reinterpret_cast<const char *>(bytes), sizeof(bytes));
return crypto::get_hash(offset, buf) == hash;
}
@@ -514,10 +525,11 @@ namespace hpfs
/**
* Process dir children response.
* @param vpath Virtual path of the fs.
* @param dir_mode Metadata 'mode' of dir.
* @param fs_entry_map Received fs entry map.
* @returns 0 on success, otherwise -1.
*/
int hpfs_sync::handle_fs_entry_response(std::string_view vpath, std::unordered_map<std::string, p2p::hpfs_fs_hash_entry> &fs_entry_map)
int hpfs_sync::handle_fs_entry_response(std::string_view vpath, const mode_t dir_mode, std::unordered_map<std::string, p2p::hpfs_fs_hash_entry> &fs_entry_map)
{
// Get the parent path of the fs entries we have received.
LOG_DEBUG << "Hpfs " << name << " sync: Processing fs entries response for " << vpath;
@@ -527,6 +539,10 @@ namespace hpfs
if (util::create_dir_tree_recursive(parent_physical_path) == -1)
return -1;
// Apply physical dir mode if received mode is different from our side.
if (apply_metadata_mode(parent_physical_path, dir_mode, true) == -1)
return -1;
// Get the children hash entries and compare with what we got from peer.
std::vector<hpfs::child_hash_node> existing_fs_entries;
if (fs_mount->get_dir_children_hashes(existing_fs_entries, hpfs::RW_SESSION_NAME, vpath) == -1)
@@ -589,12 +605,13 @@ namespace hpfs
/**
* Process file block hash map response.
* @param vpath Virtual path of the file.
* @param hash Received hash.
* @param file_mode Received metadata mode of the file.
* @param hashes Received block hashes.
* @param hash_count No. of received block hashes.
* @param file_length Size of the file.
* @returns 0 on success, otherwise -1.
*/
int hpfs_sync::handle_file_hashmap_response(std::string_view vpath, const util::h32 *hashes, const size_t hash_count, const uint64_t file_length)
int hpfs_sync::handle_file_hashmap_response(std::string_view vpath, const mode_t file_mode, const util::h32 *hashes, const size_t hash_count, const uint64_t file_length)
{
// Get the file path of the block hashes we have received.
LOG_DEBUG << "Hpfs " << name << " sync: Processing file block hashes response for " << vpath;
@@ -623,6 +640,11 @@ namespace hpfs
return -1;
}
// Apply physical file mode if received mode is different from our side.
const std::string physical_path = fs_mount->rw_dir + vpath.data();
if (apply_metadata_mode(physical_path, file_mode, false) == -1)
return -1;
return 0;
}
@@ -659,6 +681,49 @@ namespace hpfs
return 0;
}
/**
* Applies the specified to local file/dir if different. If it's a file, this will create the file
* if not exist.
* @returns 0 on success, otherwise -1.
*/
int hpfs_sync::apply_metadata_mode(std::string_view physical_path, const mode_t mode, const bool is_dir)
{
// Overlay the file/dir type flags to the permission bits.
const mode_t full_mode = (is_dir ? S_IFDIR : S_IFREG) | mode;
struct stat st;
if (stat(physical_path.data(), &st) == -1)
{
if (!is_dir && errno == ENOENT) // File does not exist. So we must create it with the given 'mode'.
{
if (mknod(physical_path.data(), full_mode, 0) == -1)
{
LOG_ERROR << errno << ": Error in creating file node. " << physical_path;
return -1;
}
else
{
return 0;
}
}
LOG_ERROR << errno << "," << ENOENT << ": Error in stat when applying file/dir mode. " << physical_path;
return -1;
}
// Reaching here means file/dir already exists. So we must apply the specified mode if it's different from current value.
if (st.st_mode != full_mode)
{
if (chmod(physical_path.data(), mode) == -1)
{
LOG_ERROR << errno << ": Error in applying file/dir mode. " << physical_path;
return -1;
}
}
return 0;
}
/**
* This method can be used to invoke mount specific custom logic (after overriding this method) to be executed after
* a sync target is acheived.