From 6eb1356220c6c7b5effcf6c57bc19266e0c07913 Mon Sep 17 00:00:00 2001 From: Sharad Boni Date: Wed, 15 Apr 2026 15:40:56 -0700 Subject: [PATCH] Fix memory safety bugs in block reading and decompression Fix three security-relevant bugs that can be triggered by crafted SST files: 1. ReadBlock integer overflow (table/format.cc): handle.size() is a uint64_t read from the SST file. On 32-bit platforms the static_cast silently truncates, and on all platforms `n + kBlockTrailerSize` can wrap to a small value, causing an undersized heap allocation followed by an out-of-bounds write. Add an overflow check before the allocation. 2. ZSTD_getFrameContentSize sentinel mishandling (port/port_stdcxx.h): The function returns ZSTD_CONTENTSIZE_UNKNOWN (SIZE_MAX) or ZSTD_CONTENTSIZE_ERROR (SIZE_MAX-1) on failure, but only the value 0 was rejected. Passing SIZE_MAX as the output buffer size to ZSTD_decompressDCtx leads to a massive allocation or undefined behavior. Check both sentinels. 3. DecodeEntry uint32 overflow (table/block.cc): The bounds check `(limit - p) < (non_shared + value_length)` performs the addition in uint32_t, which can wrap to a small value and bypass the check, leading to out-of-bounds reads. Split into two sequential checks that cannot overflow. --- port/port_stdcxx.h | 4 +++- table/block.cc | 3 ++- table/format.cc | 7 +++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/port/port_stdcxx.h b/port/port_stdcxx.h index 6f503f695b..c8e8345f40 100644 --- a/port/port_stdcxx.h +++ b/port/port_stdcxx.h @@ -164,7 +164,9 @@ inline bool Zstd_GetUncompressedLength(const char* input, size_t length, size_t* result) { #if HAVE_ZSTD size_t size = ZSTD_getFrameContentSize(input, length); - if (size == 0) return false; + if (size == 0 || size == ZSTD_CONTENTSIZE_UNKNOWN || + size == ZSTD_CONTENTSIZE_ERROR) + return false; *result = size; return true; #else diff --git a/table/block.cc b/table/block.cc index 3b1525770b..742ced8307 100644 --- a/table/block.cc +++ b/table/block.cc @@ -68,7 +68,8 @@ static inline const char* DecodeEntry(const char* p, const char* limit, if ((p = GetVarint32Ptr(p, limit, value_length)) == nullptr) return nullptr; } - if (static_cast(limit - p) < (*non_shared + *value_length)) { + if (static_cast(limit - p) < *non_shared || + static_cast(limit - p) - *non_shared < *value_length) { return nullptr; } return p; diff --git a/table/format.cc b/table/format.cc index ae998c1f30..d3eb4d9f4c 100644 --- a/table/format.cc +++ b/table/format.cc @@ -4,6 +4,8 @@ #include "table/format.h" +#include + #include "leveldb/env.h" #include "leveldb/options.h" #include "port/port.h" @@ -74,6 +76,11 @@ Status ReadBlock(RandomAccessFile* file, const ReadOptions& options, // Read the block contents as well as the type/crc footer. // See table_builder.cc for the code that built this structure. + if (handle.size() > + static_cast(std::numeric_limits::max()) - + kBlockTrailerSize) { + return Status::Corruption("block size overflow"); + } size_t n = static_cast(handle.size()); char* buf = new char[n + kBlockTrailerSize]; Slice contents;