You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

操作看似合规,为何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

执行步骤与报错信息

  1. 创建corpus子目录,其中input1文件内容为"hello"
  2. 执行命令:
./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.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 12:32:04