Commit 1cedd02a authored by Ramon Nou's avatar Ramon Nou
Browse files

fix: validate dirent buffer sizes and add shrink cutshift

Guard dirent decompression/parsing against server-reported payload sizes
that exceed the exposed client buffer to avoid unsafe reads.

Add cutshift shrink support for malleability, including survivor host id
compaction and partition redistribution for removed hosts.
parent 86f627f1
Loading
Loading
Loading
Loading
+10 −2
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@
#include <cstdint>
#include <cstring>
#include <type_traits>
#include <limits>

namespace gkfs::rpc {

@@ -54,8 +55,9 @@ namespace gkfs::rpc {
 */
template <typename OutputOrErr>
std::vector<std::tuple<const std::string, unsigned char, size_t, time_t>>
decompress_and_parse_entries(const OutputOrErr& out,
                             const void* compressed_buffer) {
decompress_and_parse_entries(
        const OutputOrErr& out, const void* compressed_buffer,
        std::size_t buffer_size = std::numeric_limits<std::size_t>::max()) {
    if(out.err != 0) {
        throw std::runtime_error("Server returned an error: " +
                                 std::to_string(out.err));
@@ -63,6 +65,12 @@ decompress_and_parse_entries(const OutputOrErr& out,
    if(out.dirents_size == 0) {
        return {};
    }
    if(out.dirents_size > buffer_size) {
        throw std::runtime_error(
                "Server returned dirents payload larger than exposed client buffer: " +
                std::to_string(out.dirents_size) + " > " +
                std::to_string(buffer_size));
    }

    const char* p = nullptr;
    const char* end = nullptr;
+14 −0
Original line number Diff line number Diff line
@@ -59,6 +59,20 @@ expand_with_cutshift(std::vector<Partition> current_partitions,
                     const std::vector<host_t>& new_hosts,
                     float old_total_capacity, float new_total_capacity);

/// Shrink the cluster with minimum data movement.
///
/// Existing survivor intervals stay owned by the same physical survivor. Output
/// host ids are compacted to the post-shrink rank order, i.e. sorted survivor
/// old ids map to 0..survivor_count-1. Only intervals owned by removed hosts
/// are redistributed to survivors to restore equal capacity.
/// @param current_partitions Current pre-shrink partition table using old host
/// ids
/// @param removed_hosts Old host ids that disappear after mutate finishes
/// @return Post-shrink compact partition table for surviving nodes
std::vector<Partition>
shrink_with_cutshift(std::vector<Partition> current_partitions,
                     const std::vector<host_t>& removed_hosts);

} // namespace rpc
} // namespace gkfs

+2 −1
Original line number Diff line number Diff line
@@ -1259,7 +1259,8 @@ gkfs_opendir(const std::string& path) {
                }

                auto entries = gkfs::rpc::decompress_and_parse_entries(
                        out, buffers[buffer_id].data());
                        out, buffers[buffer_id].data(),
                        buffers[buffer_id].size());
                consume_entries(entries);
                if(!entries.empty()) {
                    const auto& last_key = std::get<0>(entries.back());
+12 −5
Original line number Diff line number Diff line
@@ -334,6 +334,7 @@ forward_mk_symlink(const std::string& path, const std::string& target_path) {
template <typename OutputType>
std::pair<const char*, std::size_t>
decompress_dirents_payload(const OutputType& out, const void* compressed_buffer,
                           std::size_t buffer_size,
                           std::vector<char>& decompressed_data) {
    if(out.err != 0) {
        throw std::runtime_error("Server returned an error: " +
@@ -342,6 +343,12 @@ decompress_dirents_payload(const OutputType& out, const void* compressed_buffer,
    if(out.dirents_size == 0) {
        return {nullptr, 0};
    }
    if(out.dirents_size > buffer_size) {
        throw std::runtime_error(
                "Server returned dirents payload larger than exposed client buffer: " +
                std::to_string(out.dirents_size) + " > " +
                std::to_string(buffer_size));
    }

    if(gkfs::config::rpc::use_dirents_compression) {
        const unsigned long long uncompressed_size =
@@ -380,10 +387,10 @@ decompress_dirents_payload(const OutputType& out, const void* compressed_buffer,
inline std::vector<std::tuple<const std::string, unsigned char, size_t, time_t>>
decompress_and_parse_entries_standard(
        const gkfs::rpc::rpc_get_dirents_out_t& out,
        const void* compressed_buffer) {
        const void* compressed_buffer, std::size_t buffer_size) {
    std::vector<char> decompressed_data;
    auto [payload, payload_size] = decompress_dirents_payload(
            out, compressed_buffer, decompressed_data);
            out, compressed_buffer, buffer_size, decompressed_data);
    if(payload_size == 0) {
        return {};
    }
@@ -503,8 +510,8 @@ forward_get_dirents(const string& path) {
                // Decompress and parse entries
                // The decompress function expects rpc_get_dirents_out_t
                // which matches the Thallium RPC output.
                auto entries =
                        decompress_and_parse_entries_standard(out, base_ptr);
                auto entries = decompress_and_parse_entries_standard(
                        out, base_ptr, per_host_buff_size);
                for(auto& e : entries) {
                    auto type = get<1>(e);
                    gkfs::filemap::FileType ftype =
@@ -835,7 +842,7 @@ forward_get_dirents_single(const string& path, int server,
                }

                auto current_entries = gkfs::rpc::decompress_and_parse_entries(
                        out, large_buffer.data());
                        out, large_buffer.data(), buffer_size);

                if(current_entries.empty()) {
                    return make_pair(0, std::move(all_entries));
+1 −1
Original line number Diff line number Diff line
@@ -225,7 +225,7 @@ forward_get_dirents_single_proxy_v2(const string& path, int server,
            try {
                // Here we still assume the buffer is populated by RMA
                auto entries_vector = gkfs::rpc::decompress_and_parse_entries(
                        out, large_buffer.data());
                        out, large_buffer.data(), large_buffer.size());

                if(entries_vector.empty()) {
                    return make_pair(0, std::move(all_entries));
Loading