Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion include/glaze/core/reflect.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2766,8 +2766,16 @@ namespace glz
static constexpr auto N = reflect<T>::size;
static constexpr auto bsize = bucket_size(hash_type::front_hash, N);

GLZ_ALWAYS_INLINE static constexpr size_t op(auto&& it, auto&&, const size_t) noexcept
GLZ_ALWAYS_INLINE static constexpr size_t op(auto&& it, auto&& end, const size_t) noexcept
{
// front_hash is only chosen when every key is at least front_hash_bytes long, so a
// shorter key cannot match and reading the prefix would run past it. The non-padded
// readers (BSON, MessagePack, CBOR, CSV, TOML) pass end = key.data() + key.size(),
// so an unguarded read here is an out-of-bounds read of the input buffer. The
// decode_hash<JSON> front_hash variant already guards each read against end.
if (static_cast<size_t>(end - it) < HashInfo.front_hash_bytes) [[unlikely]] {
return N;
}
if constexpr (HashInfo.front_hash_bytes == 2) {
uint16_t h;
if consteval {
Expand Down
48 changes: 48 additions & 0 deletions tests/bson_test/bson_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
#include <array>
#include <chrono>
#include <cstdint>
#include <cstring>
#include <filesystem>
#include <limits>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
Expand Down Expand Up @@ -1631,4 +1633,50 @@ suite bson_skip_marker_suite = [] {
};
};

namespace bson_front_hash_tests
{
// All keys are 8 bytes and are distinguished only by their first 8 bytes, which
// selects the front_hash key lookup with front_hash_bytes == 8.
struct front_hash_keys
{
int alphaaa1{};
int alphaaa2{};
int alphaab1{};
int alphaab2{};
int betaaaa1{};
int betaaaa2{};
};
}

// A document key shorter than front_hash_bytes must not make the key hash read past
// the end of the (unpadded) input buffer. The buffer is an exact-size heap allocation
// so the over-read lands in ASAN's redzone when the bound is missing.
suite bson_front_hash_bounds_suite = [] {
using namespace bson_front_hash_tests;

"front_hash short key stays in bounds"_test = [] {
// { "x": null } : int32 len=8 | 0x0A null tag | "x"\0 | 0x00 terminator
static constexpr unsigned char doc[] = {0x08, 0x00, 0x00, 0x00, 0x0A, 0x78, 0x00, 0x00};
auto buffer = std::make_unique<char[]>(sizeof(doc));
std::memcpy(buffer.get(), doc, sizeof(doc));
const std::string_view view(buffer.get(), sizeof(doc));

front_hash_keys value{};
const auto ec = glz::read_bson(value, view);
expect(bool(ec)); // a one-byte key matches no field
};

"front_hash exact-length key still matches"_test = [] {
const front_hash_keys original{1, 2, 3, 4, 5, 6};
const auto encoded = glz::write_bson(original);
expect(encoded.has_value());

front_hash_keys decoded{};
const auto ec = glz::read_bson(decoded, *encoded);
expect(!ec);
expect(decoded.alphaaa1 == 1);
expect(decoded.betaaaa2 == 6);
};
};

int main() { return 0; }
Loading