操作看似合规,为何Clang工具仍报heap buffer overflow?
问题:Clang Fuzzer与AddressSanitizer报告堆缓冲区溢出,但代码逻辑看似无问题
在使用Clang的Fuzzer和AddressSanitizer测试memcpy时,两者均报告heap buffer overflow,但代码逻辑上似乎不存在溢出:在foo函数中创建了4字节的栈缓冲区buf,传入字符串"hello",并指定memcpy仅复制4字节到buf,理论上不会溢出,但工具仍报错。
相关源码(fuzz_string.cpp)
#include <string> #include <cstdint> #include <cstring> int foo(const char *str) { char buf[4]; memcpy(buf, str, sizeof(buf)); return 0; } extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { foo(reinterpret_cast<const char*>(data)); return 0; }
编译命令
clang++ -g -fsanitize=address,fuzzer fuzz_string.cpp -o fuzz_string
执行步骤与报错信息
- 创建
corpus子目录,其中input1文件内容为"hello" - 执行命令:
./fuzz_string corpus -max_len=1000
得到报错信息:
./fuzz_string corpus -max_len=1000 INFO: Running with entropic power schedule (0xFF, 100). INFO: Seed: 3852592942 INFO: Loaded 1 modules (2 inline 8-bit counters): 2 [0x558e0d7deed0, 0x558e0d7deed2), INFO: Loaded 1 PC tables (2 PCs): 2 [0x558e0d7deed8,0x558e0d7deef8), INFO: 1 files found in corpus ================================================================= ==3433==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000011 at pc 0x558e0d79bf20 bp 0x7ffeb3a6b750 sp 0x7ffeb3a6b748 READ of size 1 at 0x602000000011 thread T0 #0 0x558e0d79bf1f in foo(char const*) /mnt/c/Users/masch/Documents/Workbench/C/nexus-src/fuzz_string.cpp:19:14 #1 0x558e0d79c160 in LLVMFuzzerTestOneInput /mnt/c/Users/masch/Documents/Workbench/C/nexus-src/fuzz_string.cpp:30:5 #2 0x558e0d6c2323 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (/mnt/c/Users/masch/Documents/Workbench/C/nexus-src/fuzz_string+0x3e323) (BuildId: b5f5fad80b257a1e799c54575adef93091b807c4) #3 0x558e0d6c3580 in fuzzer::Fuzzer::ReadAndExecuteSeedCorpora(std::vector<fuzzer::SizedFile, std::allocator<fuzzer::SizedFile> >&) (/mnt/c/Users/masch/Documents/Workbench/C/nexus-src/fuzz_string+0x3f580) (BuildId: b5f5fad80b257a1e799c54575adef93091b807c4) #4 0x558e0d6c3bd2 in fuzzer::Fuzzer::Loop(std::vector<fuzzer::SizedFile, std::allocator<fuzzer::SizedFile> >&) (/mnt/c/Users/masch/Documents/Workbench/C/nexus-src/fuzz_string+0x3fbd2) (BuildId: b5f5fad80b257a1e799c54575adef93091b807c4) #5 0x558e0d6b1f22 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/mnt/c/Users/masch/Documents/Workbench/C/nexus-src/fuzz_string+0x2df22) (BuildId: b5f5fad80b257a1e799c54575adef93091b807c4) #6 0x558e0d6dbc12 in main (/mnt/c/Users/masch/Documents/Workbench/C/nexus-src/fuzz_string+0x57c12) (BuildId: b5f5fad80b257a1e799c54575adef93091b807c4) #7 0x7ff7481b6d8f (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 203de0ae33b53fee1578b117cb4123e85d0534f0) #8 0x7ff7481b6e3f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x29e3f) (BuildId: 203de0ae33b53fee1578b117cb4123e85d0534f0) #9 0x558e0d6a6964 in _start (/mnt/c/Users/masch/Documents/Workbench/C/nexus-src/fuzz_string+0x22964) (BuildId: b5f5fad80b257a1e799c54575adef93091b807c4) 0x602000000011 is located 0 bytes to the right of 1-byte region [0x602000000010,0x602000000011) allocated by thread T0 here: #0 0x558e0d79985d in operator new[](unsigned long) (/mnt/c/Users/masch/Documents/Workbench/C/nexus-src/fuzz_string+0x11585d) (BuildId: b5f5fad80b257a1e799c54575adef93091b807c4) #1 0x558e0d6c2232 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (/mnt/c/Users/masch/Documents/Workbench/C/nexus-src/fuzz_string+0x3e232) (BuildId: b5f5fad80b257a1e799c54575adef93091b807c4) #2 0x558e0d6c3580 in fuzzer::Fuzzer::ReadAndExecuteSeedCorpora(std::vector<fuzzer::SizedFile, std::allocator<fuzzer::SizedFile> >&) (/mnt/c/Users/masch/Documents/Workbench/C/nexus-src/fuzz_string+0x3f580) (BuildId: b5f5fad80b257a1e799c54575adef93091b807c4) #3 0x558e0d6c3bd2 in fuzzer::Fuzzer::Loop(std::vector<fuzzer::SizedFile, std::allocator<fuzzer::SizedFile> >&) (/mnt/c/Users/masch/Documents/Workbench/C/nexus-src/fuzz_string+0x3fbd2) (BuildId: b5f5fad80b257a1e799c54575adef93091b807c4) #4 0x558e0d6b1f22 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/mnt/c/Users/masch/Documents/Workbench/C/nexus-src/fuzz_string+0x2df22) (BuildId: b5f5fad80b257a1e799c54575adef93091b807c4) #5 0x558e0d6dbc12 in main (/mnt/c/Users/masch/Documents/Workbench/C/nexus-src/fuzz_string+0x57c12) (BuildId: b5f5fad80b257a1e799c54575adef93091b807c4) #6 0x7ff7481b6d8f (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 203de0ae33b53fee1578b117cb4123e85d0534f0) SUMMARY: AddressSanitizer: heap-buffer-overflow /mnt/c/Users/masch/Documents/Workbench/C/nexus-src/fuzz_string.cpp:19:14 in foo(char const*) Shadow bytes around the buggy address: 0x0c047fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c047fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c047fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c047fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c047fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x0c047fff8000: fa fa[01]fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c047fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c047fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c047fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c047fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c047fff8050: 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 ==3433==ABORTING MS: 0 ; base unit: 0000000000000000000000000000000000000000 artifact_prefix='./'; Test unit written to ./crash-da39a3ee5e6b4b0d3255bfef95601890afd80709 Base64:
原因解析
你看到的堆溢出并非来自foo函数中的栈缓冲区buf,而是LLVM Fuzzer内部的堆分配数据被越界读取。
仔细分析报错信息:
- 溢出地址
0x602000000011属于一个1字节的堆区域,由operator new[]分配,分配栈帧显示是Fuzzer的ExecuteCallback函数内部操作。 - 报错指向
foo函数第19行的memcpy,但实际是memcpy在读取源数据str时越界了——当Fuzzer传入的测试数据长度小于sizeof(buf)(4字节)时,memcpy会尝试读取超出数据实际长度的内存,触发堆缓冲区溢出。
换句话说,你只限制了复制到目标缓冲区的长度,但没有限制源数据的可用长度。当Fuzzer生成或传入长度不足4字节的测试用例时,memcpy会从str指向的堆内存中读取超出分配范围的字节,导致ASan报错。
修复方案
在memcpy时取sizeof(buf)和输入数据长度的最小值,确保不会读取超出源数据范围的内存:
#include <string> #include <cstdint> #include <cstring> #include <algorithm> // 引入std::min int foo(const char *str, size_t str_len) { char buf[4]; memcpy(buf, str, std::min(sizeof(buf), str_len)); return 0; } extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { foo(reinterpret_cast<const char*>(data), size); return 0; }
内容的提问来源于stack exchange,提问作者MaSc. H.
相关产品推荐
相关产品推荐

