代码之家  ›  专栏  ›  技术社区  ›  MaSc. H.

当一切看起来都很好的时候,为什么这是堆缓冲区溢出?

  •  0
  • MaSc. H.  · 技术社区  · 1 年前

    当我在叮当声中使用Fuzzer和Adress消毒液并进行测试时 memcpy ,两者都报告了堆缓冲区溢出,但我实际上无法解释自己“为什么”。在名为的伪函数内 foo ,我创建了一个名为的缓冲区 buf 大小为4字节。函数foo接受一个名为 str 顾名思义,它应该包含一个字符串。在本例中,我将字符串“hello”传递给了foo。由于buf中只有4个字节,所以我还指示memcpy始终只从参数str中复制4个字节。否则会溢出。

    但是,为什么Clang fuzzer/AdressSanitizer报告堆溢出?对我来说一切似乎都很好。。。我从一个较大的字符串(“hello”)中只复制了4个字节到一个缓冲区(buf)中,该缓冲区无论如何只能容纳4个字节。。

    要创建错误,这是源文件 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:
    

    我试着在谷歌上搜索例子或解释,但不幸的是,没有人能帮我。这 link 这让我对这个话题很好奇,但无法正确解释为什么当我们只将4字节复制到4字节的目标中时会导致堆溢出。

    1 回复  |  直到 1 年前
        1
  •  1
  •   Allan Wind    1 年前

    在C++中,更喜欢使用 std::string std::vector 而不是 char * .

    在C中(正如最初标记的那样)如果 str 是一个字符串( '\0' terminated),则代码将溢出 str 如果 strlen(str) < 3 。我建议你使用 strncpy() 并确保 '\0' 结束:

    int foo(const char *str) {
        if(!str) return !0; // ?
        char buf[4];
        strncpy(buf, str, sizeof buf - 1);
        buf[sizeof buf - 1] = '\0';
        return 0;
    }
    

    或者使用 memcpy() 并确保 '\0' 结束。你需要保持 n 绕以随后使用字节数组:

    size_t n = strlen(str) < sizeof buf ? strlen(str) : sizeof buf;
    memcpy(buf, str, n);
    

    如果 str 不是字符串,则无法在中修复此问题 foo() 。我注意到打电话的人有一个 size_t 这对我来说有点暗示 char 数组(而不是字符串)。