hiredis icon indicating copy to clipboard operation
hiredis copied to clipboard

heap-buffer-overflow in sdscatlen

Open Jminis opened this issue 2 years ago • 0 comments

Hello,

I encountered a crash while performing my fuzzing tasks, which prompted me to leave this message.

have a good day :)

📌 Main Information

Project hiredis
Fuzzer libfuzzer
Fuzz binary format_command_fuzzer
Sanitizer asan
Crash Type Heap-buffer-overflow
Crash State sdscatlen in /src/hiredis/sds.c:386:5
=================================================================
==643==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x603000000145 at pc 0x00000052e371 bp 0x7fffffffd7d0 sp 0x7fffffffcfa0
READ of size 260 at 0x603000000145 thread T0
SCARINESS: 26 (multi-byte-read-heap-buffer-overflow)
[Detaching after fork from child process 645]
    #0 0x52e370 in __asan_memcpy /src/llvm-project/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cpp:22:3
    #1 0x57ab16 in sdscatlen /src/hiredis/sds.c:386:5
    #2 0x56d2de in redisvFormatCommand /src/hiredis/hiredis.c
    #3 0x56f2cd in redisFormatCommand /src/hiredis/hiredis.c:569:11
    #4 0x56c53e in LLVMFuzzerTestOneInput /src/hiredis/format_command_fuzzer.c:51:9
    #5 0x43de53 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:611:15
    #6 0x4295b2 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:324:6
    #7 0x42ee5c in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:860:9
    #8 0x458392 in main /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerMain.cpp:20:10
    #9 0x7ffff7c58082 in __libc_start_main /build/glibc-SzIz7B/glibc-2.31/csu/../csu/libc-start.c:308:16
    #10 0x41f77d in _start (/out/hiredis/format_command_fuzzer+0x41f77d)

DEDUP_TOKEN: __asan_memcpy--sdscatlen--redisvFormatCommand
0x603000000145 is located 0 bytes to the right of 21-byte region [0x603000000130,0x603000000145)
allocated by thread T0 here:
    #0 0x52efe6 in __interceptor_malloc /src/llvm-project/compiler-rt/lib/asan/asan_malloc_linux.cpp:69:3
    #1 0x56c4f0 in LLVMFuzzerTestOneInput /src/hiredis/format_command_fuzzer.c:44:15
    #2 0x43de53 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:611:15
    #3 0x4295b2 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:324:6
    #4 0x42ee5c in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:860:9
    #5 0x458392 in main /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerMain.cpp:20:10
    #6 0x7ffff7c58082 in __libc_start_main /build/glibc-SzIz7B/glibc-2.31/csu/../csu/libc-start.c:308:16

DEDUP_TOKEN: __interceptor_malloc--LLVMFuzzerTestOneInput--fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long)
SUMMARY: AddressSanitizer: heap-buffer-overflow /src/llvm-project/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cpp:22:3 in __asan_memcpy
Shadow bytes around the buggy address:
  0x0c067fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c067fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c067fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c067fff8000: fa fa 00 00 00 fa fa fa 00 00 00 fa fa fa 00 00
  0x0c067fff8010: 00 fa fa fa 00 00 00 00 fa fa 00 00 04 fa fa fa
=>0x0c067fff8020: 00 00 04 fa fa fa 00 00[05]fa fa fa fa fa fa fa
  0x0c067fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c067fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c067fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c067fff8060: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c067fff8070: 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
==643==ABORTING
[Thread 0x7ffff40f9700 (LWP 644) exited]
[Inferior 1 (process 643) exited with code 01]

🎯 Fuzz target Information

  • format_command_fuzzer.c
#include <stdlib.h>
#include <string.h>
#include "hiredis.h"

int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { char *new_str, *cmd;

if (size &lt; 3)
    return 0;

new_str = malloc(size+1);
if (new_str == NULL)
    return 0;

memcpy(new_str, data, size);
new_str[size] = '\\0';

if (redisFormatCommand(&amp;cmd, new_str) != -1)
    hi_free(cmd);

free(new_str);
return 0;

}

  • Crash data
24 68 78 25 25 25 62 22 20 22 20 25 08 01 20 01 20 81 68 78

🧐 Analysis

  • The function is invoked in the following order with the input data $hx%%%b…:
    • $newarg = sdscatlen(curarg, c, 1);
    • hnewarg = sdscatlen(curarg, c, 1);
    • xnewarg = sdscatlen(curarg, c, 1);
    • %%newarg = sdscat(curarg, "%");
    • %bnewarg = sdscatlen(curarg, arg, size);
  • The crash occurs in the memcpy inside the last sdscatlen call, and the cause is that the size value is larger than the length of the input data.
  • The size value is initialized during the size = va_arg(ap, size_t); step before the sdscatlen function is called.
sds sdscatlen(sds s, const void *t, size_t len) {
    size_t curlen = sdslen(s);
s = sdsMakeRoomFor(s,len);
if (s == NULL) return NULL;
memcpy(s+curlen, t, len); // [*] -- crash state
sdssetlen(s, curlen+len);
s[curlen+len] = '\\0';
return s;

}

int redisvFormatCommand(char **target, const char *format, va_list ap) {
/* skip */
    while(*c != '\\0') {
        if (*c != '%' || c[1] == '\\0') {
            if (*c == ' ') {
                if (touched) {
                    newargv = hi_realloc(curargv,sizeof(char*)*(argc+1));
                    if (newargv == NULL) goto memory_err;
                    curargv = newargv;
                    curargv[argc++] = curarg;
                    totlen += bulklen(sdslen(curarg));

                    /* curarg is put in argv so it can be overwritten. */
                    curarg = sdsempty();
                    if (curarg == NULL) goto memory_err;
                    touched = 0;
                }
            } else {
                newarg = sdscatlen(curarg,c,1);
                if (newarg == NULL) goto memory_err;
                curarg = newarg;
                touched = 1;
            }
        } else {
            char *arg;
            size_t size;

            /* Set newarg so it can be checked even if it is not touched. */
            newarg = curarg;

            switch(c[1]) {
            case 's':
                arg = va_arg(ap,char*);
                size = strlen(arg);
                if (size > 0)
                    newarg = sdscatlen(curarg,arg,size);
                break;
            case 'b':
                arg = va_arg(ap,char*);
                size = va_arg(ap,size_t);
                if (size > 0)
                    newarg = sdscatlen(curarg,arg,size); // [*] -- crash func call
                break;
            case '%':
                newarg = sdscat(curarg,"%");
                break;
/* skip */
}
  • If a larger memcpy occurs than the size of the input data, it is expected that arbitrary data will be copied into the buffer.

debug

Jminis avatar May 17 '23 06:05 Jminis