libucl icon indicating copy to clipboard operation
libucl copied to clipboard

Bug: AddressSanitizer: heap-buffer-overflow found with Libfuzzer

Open tangjm24 opened this issue 7 months ago • 0 comments

To be short, I have found several issues using libfuzzer fuzz tools. Here I list some clue about how to preproduce and fix those issues.

Environment

libucl version: Latest commit 3e7f023 System: Ubuntu 22.04.5 LTS (Jammy) Kernel/Release: 22.04


Bug Reproduction

driver code

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
    struct ucl_parser *p = ucl_parser_new(
        UCL_PARSER_NO_FILEVARS);  // 开启更多功能:支持宏等

    if (!p) return 0;

    if (!ucl_parser_add_chunk(p, (const unsigned char *)data, size)) {
        const char *err = ucl_parser_get_error(p);  // 保留错误信息供调试
        (void)err;
        ucl_parser_free(p);
        return 0;
    }

    ucl_object_t *obj = ucl_parser_get_object(p);
    if (!obj) {
        const char *err = ucl_parser_get_error(p);
        (void)err;
        ucl_parser_free(p);
        return 0;
    }

    // 🚀 Emit in different formats to hit more code paths
    unsigned char *json = ucl_object_emit(obj, UCL_EMIT_JSON_COMPACT);
    unsigned char *yaml = ucl_object_emit(obj, UCL_EMIT_YAML);
    unsigned char *config = ucl_object_emit(obj, UCL_EMIT_CONFIG);

    free(json);
    free(yaml);
    free(config);

    // 🔁 Iterate all elements deeply
    ucl_object_iter_t it = NULL;
    const ucl_object_t *cur;

    while ((cur = ucl_iterate_object(obj, &it, true)) != NULL) {
        (void)ucl_object_tostring(cur);
        (void)ucl_object_toint(cur);
        (void)ucl_object_todouble(cur);
        (void)ucl_object_toboolean(cur);
        (void)ucl_object_type(cur);
        // (void)ucl_object_len(cur);

        // 🧪 Validate subobject to itself
        (void)ucl_object_validate(cur, cur, NULL);
    }

    // 🧬 Re-parse emitted JSON to catch serialization bugs
    unsigned char *reemit = ucl_object_emit(obj, UCL_EMIT_JSON);
    if (reemit) {
        struct ucl_parser *p2 = ucl_parser_new(UCL_PARSER_NO_FILEVARS);
        if (p2) {
            (void)ucl_parser_add_chunk(p2, reemit, strlen((const char *)reemit));
            ucl_object_t *reobj = ucl_parser_get_object(p2);
            if (reobj) {
                ucl_object_unref(reobj);
            }
            ucl_parser_free(p2);
        }
        free(reemit);
    }

    ucl_object_unref(obj);
    ucl_parser_free(p);
    return 0;
}

compile:

cd .. && rm -rf libucl-0.9.2 && tar -xvf libucl-0.9.2.tar.gz && cd libucl-0.9.2/

LIB_CONFIG_BASE_DIR=$(pwd)
INSTALL_PREFIX="${LIB_CONFIG_BASE_DIR}/libucl_install"  
echo "Libconfig will be installed to: ${INSTALL_PREFIX}"
mkdir -p "${INSTALL_PREFIX}"  

# if [! -f configure ]; then
./autogen.sh
# fi
echo "Configuring libucl..."

CFLAGS="-g -O1 -fsanitize=address" \
CXXFLAGS="-g -O1 -fsanitize=address" \
./configure --prefix="${INSTALL_PREFIX}" --enable-static=yes --enable-shared=no  

make clean && make -j$(nproc) && make install

# CC=$AFL_HOME/afl-clang
# CXX=$AFL_HOME/afl-clang++

cd "../libucl_test"
echo $INSTALL_PREFIX/include
clang++ -g -O1 -fsanitize=address,fuzzer main.cc \
    ../libucl-0.9.2/libucl_install/lib/libucl.a\
    -I$INSTALL_PREFIX/include \
    -L$INSTALL_PREFIX/lib \
    -o libconfig_fuzzer

# # $AFL_HOME/afl-fuzz -m none -i corpus -o out ./libucl_fuzzer @@

./ucl_libfuzzer ./corpus/ > fuze-0.log 2>&1

[crash.txt](

crash-ca6d7f3f4c77eb59c507be963de3c344261d5c2e.txt

)


Fix Recommondation

crash info

==12111==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6020010db2bc at pc 0x000000566155 bp 0x7ffe729b6180 sp 0x7ffe729b6178
READ of size 1 at 0x6020010db2bc thread T0
    #0 0x566154  (/data/tjm/code/fuzz/libucl_test/ucl_libfuzzer+0x566154)
    #1 0x5704e1  (/data/tjm/code/fuzz/libucl_test/ucl_libfuzzer+0x5704e1)
    #2 0x56be45  (/data/tjm/code/fuzz/libucl_test/ucl_libfuzzer+0x56be45)
    #3 0x5504c8  (/data/tjm/code/fuzz/libucl_test/ucl_libfuzzer+0x5504c8)
    #4 0x430377  (/data/tjm/code/fuzz/libucl_test/ucl_libfuzzer+0x430377)
    #5 0x43abe4  (/data/tjm/code/fuzz/libucl_test/ucl_libfuzzer+0x43abe4)
    #6 0x43c24f  (/data/tjm/code/fuzz/libucl_test/ucl_libfuzzer+0x43c24f)
    #7 0x42b60c  (/data/tjm/code/fuzz/libucl_test/ucl_libfuzzer+0x42b60c)
    #8 0x41d4f2  (/data/tjm/code/fuzz/libucl_test/ucl_libfuzzer+0x41d4f2)
    #9 0x7fa59ff67c86  (/lib/x86_64-linux-gnu/libc.so.6+0x21c86)
    #10 0x41e549  (/data/tjm/code/fuzz/libucl_test/ucl_libfuzzer+0x41e549)

0x6020010db2bc is located 0 bytes to the right of 12-byte region [0x6020010db2b0,0x6020010db2bc)
allocated by thread T0 here:
    #0 0x54c1d0  (/data/tjm/code/fuzz/libucl_test/ucl_libfuzzer+0x54c1d0)
    #1 0x43028e  (/data/tjm/code/fuzz/libucl_test/ucl_libfuzzer+0x43028e)
    #2 0x43abe4  (/data/tjm/code/fuzz/libucl_test/ucl_libfuzzer+0x43abe4)
    #3 0x43c24f  (/data/tjm/code/fuzz/libucl_test/ucl_libfuzzer+0x43c24f)
    #4 0x42b60c  (/data/tjm/code/fuzz/libucl_test/ucl_libfuzzer+0x42b60c)
    #5 0x41d4f2  (/data/tjm/code/fuzz/libucl_test/ucl_libfuzzer+0x41d4f2)
    #6 0x7fa59ff67c86  (/lib/x86_64-linux-gnu/libc.so.6+0x21c86)

SUMMARY: AddressSanitizer: heap-buffer-overflow (/data/tjm/code/fuzz/libucl_test/ucl_libfuzzer+0x566154) 
Shadow bytes around the buggy address:
  0x0c0480213600: fa fa fd fd fa fa fd fa fa fa fd fa fa fa fd fa
  0x0c0480213610: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fa
  0x0c0480213620: fa fa fd fa fa fa fd fd fa fa fd fa fa fa fd fa
  0x0c0480213630: fa fa fd fa fa fa fd fd fa fa fd fd fa fa fd fd
  0x0c0480213640: fa fa fd fa fa fa fd fa fa fa fd fd fa fa fd fa
=>0x0c0480213650: fa fa fd fa fa fa 00[04]fa fa 00 fa fa fa 00 04
  0x0c0480213660: fa fa 00 01 fa fa 00 01 fa fa 05 fa fa fa 00 fa
  0x0c0480213670: fa fa 00 01 fa fa 06 fa fa fa 07 fa fa fa 02 fa
  0x0c0480213680: fa fa 04 fa fa fa 02 fa fa fa 02 fa fa fa 02 fa
  0x0c0480213690: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c04802136a0: 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
==12111==ABORTING
MS: 1 CopyPart-; base unit: f3978f4b9b9cf75b371ab53510f8b25c6d0ae567
0x30,0x20,0x34,0xa,0x64,0x20,0x74,0x3b,0x72,0x20,0x30,0x20,
0 4\x0ad t;r 0 
artifact_prefix='./'; Test unit written to ./crash-ca6d7f3f4c77eb59c507be963de3c344261d5c2e
Base64: MCA0CmQgdDtyIDAg

how to fix? change libucl-0.9.2/src/ucl_parser.c:1010

	case ' ':
		while (p < end && ucl_test_character(*p, UCL_CHARACTER_WHITESPACE)) {
			p++;
		}
		if (ucl_lex_is_atom_end(*p))
			goto set_obj;
		break;

to

	case ' ':
		while (p < end && ucl_test_character(*p, UCL_CHARACTER_WHITESPACE)) {
			p++;
		}
                if (!p) break;
		if (ucl_lex_is_atom_end(*p))
			goto set_obj;
		break;

tangjm24 avatar Jun 02 '25 09:06 tangjm24