diff --git a/tuple/include/tuple_sketch_impl.hpp b/tuple/include/tuple_sketch_impl.hpp index b3f2d0bd..3a9d2b75 100644 --- a/tuple/include/tuple_sketch_impl.hpp +++ b/tuple/include/tuple_sketch_impl.hpp @@ -583,6 +583,7 @@ compact_tuple_sketch compact_tuple_sketch::deserialize(const void* b std::unique_ptr summary(alloc.allocate(1), deleter_of_summaries(1, false, allocator)); for (size_t i = 0; i < num_entries; ++i) { uint64_t key; + ensure_minimum_memory(base + size - ptr, sizeof(uint64_t)); ptr += copy_from_mem(ptr, key); ptr += sd.deserialize(ptr, base + size - ptr, summary.get(), 1); entries.emplace_back(key, std::move(*summary)); diff --git a/tuple/test/tuple_sketch_test.cpp b/tuple/test/tuple_sketch_test.cpp index 6e44e284..30c415f9 100644 --- a/tuple/test/tuple_sketch_test.cpp +++ b/tuple/test/tuple_sketch_test.cpp @@ -371,4 +371,19 @@ TEST_CASE("filter", "[tuple_sketch]") { } } +TEST_CASE("tuple sketch: deserialize bounds-checks each entry key", "[tuple_sketch]") { + // A compact sketch serialized with a narrower summary (float, 4 bytes) and then + // deserialized as a wider summary (double, 8 bytes). The per-entry stride the reader + // assumes (8-byte key + 8-byte summary) is larger than the entries actually occupy + // (8-byte key + 4-byte summary), so the read cursor advances past the end of the buffer. + // num_entries is read from the preamble and is unaffected, so the entry loop still runs + // the full count and the per-entry key read walks off the end. This must throw rather + // than read out of bounds (a heap-buffer-overflow under AddressSanitizer). + auto update_sketch = update_tuple_sketch::builder().build(); + for (int i = 0; i < 100; ++i) update_sketch.update(i, 1.0f); + auto bytes = update_sketch.compact().serialize(); + REQUIRE_THROWS_AS(compact_tuple_sketch::deserialize(bytes.data(), bytes.size()), + std::out_of_range); +} + } /* namespace datasketches */