-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Closed
Description
There is a potential UAF when BrotliEncoderPrepareDictionary retains a stale pointer to the original dictionary buffer passed in. Other APIs have explicitly documented lifetime rules about ownership, but I couldn't find any comments/documentation about BrotliEncoderPrepareDictionary indicating the original buffer is expected to be kept alive.
Tested on the most recent commit: 85d46ce6
As a user I would expect either:
BrotliEncoderPrepareDictionarymakes a copy of the underlying buffer, and does not retain a pointer to the original source (thus users can free it)- The lifetime requirement is explicitly documented somewhere
The following testcase triggers an ASAN violation caused by retaining a pointer to an underlying dictionary buffer which is freed before trying to perform compression:
testcase
#include <cstdint>
#include <cstring>
#include <string>
#include <vector>
extern "C" {
#include "brotli/encode.h"
}
int main(){
// Serialized dictionary bytes (first 115 bytes from the original reproducer)
const uint8_t dict_bytes[115] = {
66,82,83,68,1,0,5,0,8,0,10,0,97,81,77,85,45,55,67,121,82,112,0,0,0,0,3,0,76,54,100,0,0,27,0,178,214,247,225,95,133,228,247,4,46,184,80,61,96,134,160,126,246,1,129,131,143,116,253,138,204,172,30,0,63,164,158,165,46,220,53,169,213,146,118,50,17,174,123,243,91,168,231,143,185,77,35,160,46,192,92,234,16,90,14,0,99,76,71,80,100,113,51,121,52,76,72,57,69,51,0,199,221,182,211
};
BrotliEncoderPreparedDictionary* dict = nullptr;
{
// tmp's storage is freed at end of this scope
std::string tmp(reinterpret_cast<const char*>(dict_bytes), sizeof(dict_bytes));
dict = BrotliEncoderPrepareDictionary((BrotliSharedDictionaryType)0,
tmp.size(),
reinterpret_cast<const uint8_t*>(tmp.data()),
0,
nullptr,
nullptr,
nullptr);
if (!dict) return 0; // bail if dictionary parse fails
} // tmp destroyed here; if library retained pointer, UAF will occur later
BrotliEncoderState* st = BrotliEncoderCreateInstance(nullptr,nullptr,nullptr);
if (!st) return 0;
(void)BrotliEncoderAttachPreparedDictionary(st, dict);
// Drive encoder with enough data and FINISH to exercise HQ Zopfli path
std::vector<uint8_t> in(2303);
for (size_t i=0;i<in.size();++i) in[i] = static_cast<uint8_t>((i*131u+7u)&0xFF);
const uint8_t* next_in = in.data();
size_t avail_in = in.size();
uint8_t out_buf[64];
uint8_t* next_out = out_buf;
size_t avail_out = sizeof(out_buf);
size_t total_out = 0;
while (true) {
BROTLI_BOOL ok = BrotliEncoderCompressStream(st, BROTLI_OPERATION_FINISH,
&avail_in, &next_in,
&avail_out, &next_out,
&total_out);
if (!ok) break;
if (!BrotliEncoderHasMoreOutput(st) && avail_in == 0) break;
if (avail_out == 0) { next_out = out_buf; avail_out = sizeof(out_buf); }
}
return 0;
}report
{
"Date": "2025-09-21T16:23:03.968754+00:00",
"Uname": "Linux 6d89288ba001 5.15.0-140-generic #150-Ubuntu SMP Sat Apr 12 06:00:09 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux",
"OS": "Ubuntu",
"OSRelease": "22.04",
"Architecture": "amd64",
"ExecutablePath": "/tmp/tmp87su4xhj/reproducer",
"ProcEnviron": [
"LIBAFL_EDGES_MAP_SIZE=800000",
"PWD=/fuzz/workspace",
"CXX=gf_libafl_cxx",
"GRAPHFUZZ_USE_ASAN=1",
"HOME=/root",
"ASAN_OPTIONS=hard_rss_limit_mb=1024:detect_leaks=0",
"TERM=xterm-256color",
"SHLVL=1",
"LD_LIBRARY_PATH=/fuzz/install/lib",
"PATH=/root/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"CC=gf_libafl_cc",
"DEBIAN_FRONTEND=noninteractive",
"OLDPWD=/build",
"_=/usr/local/bin/agfi"
],
"ProcCmdline": "/tmp/tmp87su4xhj/reproducer",
"Stdin": "",
"ProcStatus": [],
"ProcMaps": [],
"ProcFiles": [],
"NetworkConnections": [],
"CrashSeverity": {
"Type": "NOT_EXPLOITABLE",
"ShortDescription": "heap-use-after-free(read)",
"Description": "Use of deallocated memory",
"Explanation": "The target crashed when reading from memory after it has been freed."
},
"Stacktrace": [
" #0 0x55555574e379 in UpdateNodes /fuzz/src/c/enc/backward_references_hq.c:502:29",
" #1 0x5555557571e3 in ZopfliIterate /fuzz/src/c/enc/backward_references_hq.c:664:19",
" #2 0x5555557571e3 in BrotliCreateHqZopfliBackwardReferences /fuzz/src/c/enc/backward_references_hq.c:924:22",
" #3 0x555555671077 in EncodeData /fuzz/src/c/enc/encode.c:1104:5",
" #4 0x555555663847 in BrotliEncoderCompressStream /fuzz/src/c/enc/encode.c:1685:18",
" #5 0x55555565ec16 in main /tmp/tmp87su4xhj/reproducer.cpp:41:22",
" #6 0x7ffff7a6dd8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16",
" #7 0x7ffff7a6de3f in __libc_start_main csu/../csu/libc-start.c:392:3",
" #8 0x5555555834c4 in _start (/tmp/tmp87su4xhj/reproducer+0x2f4c4) (BuildId: e65d5dd2e799f4fc3d57d2a874ca39d45f905fac)"
],
"Registers": {},
"Disassembly": [],
"Package": "",
"PackageVersion": "",
"PackageArchitecture": "",
"PackageDescription": "",
"AsanReport": [
"==156==ERROR: AddressSanitizer: heap-use-after-free on address 0x50c0000000b0 at pc 0x55555574e37a bp 0x7fffffffdec0 sp 0x7fffffffdeb8",
"READ of size 1 at 0x50c0000000b0 thread T0",
" #0 0x55555574e379 in UpdateNodes /fuzz/src/c/enc/backward_references_hq.c:502:29",
" #1 0x5555557571e3 in ZopfliIterate /fuzz/src/c/enc/backward_references_hq.c:664:19",
" #2 0x5555557571e3 in BrotliCreateHqZopfliBackwardReferences /fuzz/src/c/enc/backward_references_hq.c:924:22",
" #3 0x555555671077 in EncodeData /fuzz/src/c/enc/encode.c:1104:5",
" #4 0x555555663847 in BrotliEncoderCompressStream /fuzz/src/c/enc/encode.c:1685:18",
" #5 0x55555565ec16 in main /tmp/tmp87su4xhj/reproducer.cpp:41:22",
" #6 0x7ffff7a6dd8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16",
" #7 0x7ffff7a6de3f in __libc_start_main csu/../csu/libc-start.c:392:3",
" #8 0x5555555834c4 in _start (/tmp/tmp87su4xhj/reproducer+0x2f4c4) (BuildId: e65d5dd2e799f4fc3d57d2a874ca39d45f905fac)",
"",
"0x50c0000000b0 is located 112 bytes inside of 116-byte region [0x50c000000040,0x50c0000000b4)",
"freed by thread T0 here:",
" #0 0x55555565cb3d in operator delete(void*) (/tmp/tmp87su4xhj/reproducer+0x108b3d) (BuildId: e65d5dd2e799f4fc3d57d2a874ca39d45f905fac)",
" #1 0x55555565e82d in __gnu_cxx::new_allocator<char>::deallocate(char*, unsigned long) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/ext/new_allocator.h:145:2",
" #2 0x55555565e82d in std::allocator_traits<std::allocator<char>>::deallocate(std::allocator<char>&, char*, unsigned long) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/alloc_traits.h:496:13",
" #3 0x55555565e82d in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>::_M_destroy(unsigned long) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/basic_string.h:245:9",
" #4 0x55555565e82d in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>::_M_dispose() /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/basic_string.h:240:4",
" #5 0x55555565e82d in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>::~basic_string() /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/basic_string.h:672:9",
" #6 0x55555565e82d in main /tmp/tmp87su4xhj/reproducer.cpp:25:3",
" #7 0x7ffff7a6dd8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16",
"",
"previously allocated by thread T0 here:",
" #0 0x55555565c2dd in operator new(unsigned long) (/tmp/tmp87su4xhj/reproducer+0x1082dd) (BuildId: e65d5dd2e799f4fc3d57d2a874ca39d45f905fac)",
" #1 0x55555565e723 in void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>::_M_construct<char const*>(char const*, char const*, std::forward_iterator_tag) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/basic_string.tcc:219:14",
" #2 0x55555565e723 in void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>::_M_construct_aux<char const*>(char const*, char const*, std::__false_type) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/basic_string.h:255:11",
" #3 0x55555565e723 in void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>::_M_construct<char const*>(char const*, char const*) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/basic_string.h:274:4",
" #4 0x55555565e723 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>::basic_string(char const*, unsigned long, std::allocator<char> const&) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/basic_string.h:521:9",
" #5 0x55555565e723 in main /tmp/tmp87su4xhj/reproducer.cpp:16:17",
" #6 0x7ffff7a6dd8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16",
"",
"SUMMARY: AddressSanitizer: heap-use-after-free /fuzz/src/c/enc/backward_references_hq.c:502:29 in UpdateNodes",
"Shadow bytes around the buggy address:",
" 0x50bffffffe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
" 0x50bffffffe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
" 0x50bfffffff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
" 0x50bfffffff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
" 0x50c000000000: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd",
"=>0x50c000000080: fd fd fd fd fd fd[fd]fa fa fa fa fa fa fa fa fa",
" 0x50c000000100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa",
" 0x50c000000180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa",
" 0x50c000000200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa",
" 0x50c000000280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa",
" 0x50c000000300: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa",
"Shadow byte legend (one shadow byte represents 8 application bytes):",
" Addressable: 00",
" Partially addressable: 01 02 03 04 05 06 07",
" Heap left redzone: fa",
" Freed heap region: fd",
" Stack left redzone: f1",
" Stack mid redzone: f2",
" Stack right redzone: f3",
" Stack after return: f5",
" Stack use after scope: f8",
" Global redzone: f9",
" Global init order: f6",
" Poisoned by user: f7",
" Container overflow: fc",
" Array cookie: ac",
" Intra object redzone: bb",
" ASan internal: fe",
" Left alloca redzone: ca",
" Right alloca redzone: cb",
"==156==ABORTING"
],
"MsanReport": [],
"UbsanReport": [],
"LuaReport": [],
"PythonReport": [],
"GoReport": [],
"JavaReport": [],
"RustReport": [],
"JsReport": [],
"CSharpReport": [],
"CrashLine": "/fuzz/src/c/enc/backward_references_hq.c:502:29",
"Source": [
" 498 offset = offset - addon->chunk_offsets[d] - backward;",
" 499 limit = addon->chunk_offsets[d + 1] - addon->chunk_offsets[d] - offset;",
" 500 limit = limit > max_len ? max_len : limit;",
" 501 if (best_len >= limit ||",
"--->502 continuation != source[offset + best_len]) {",
" 503 continue;",
" 504 }",
" 505 len = FindMatchLengthWithLimit(&source[offset],",
" 506 &ringbuffer[cur_ix_masked],",
" 507 limit);"
]
}Metadata
Metadata
Assignees
Labels
No labels