View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0000689 | file | General | public | 2025-10-27 03:20 | 2025-11-04 20:43 |
| Reporter | weijinjin | Assigned To | christos | ||
| Priority | high | Severity | major | Reproducibility | always |
| Status | resolved | Resolution | fixed | ||
| Product Version | 5.46 | ||||
| Fixed in Version | HEAD | ||||
| Summary | 0000689: Calling file_printf(ms, fmt) with a heap format string that ends in an incomplete specifier (e.g., "%") triggers a heap-buffer-o | ||||
| Description | # Bug Report ## Describe the bug Calling file_printf(ms, fmt) with a heap format string that ends in an incomplete specifier (e.g., "%") triggers a heap-buffer-over-read in libmagic’s file_checkfmt() (funcs.c). The parser walks past the terminating NUL and dereferences one byte beyond the allocated buffer while scanning flags/length modifiers. ASan shows: ``` ==1938837==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x50b0000000a5 READ of size 1 at 0x50b0000000a5 thread T0 #0 file_checkfmt funcs.c:103 0000001 file_vprintf funcs.c:142 0000002 file_printf funcs.c:182 ... 0x50b0000000a5 is 0 bytes after 101-byte region [0x50b000000040,0x50b0000000a5) ``` That is, fmt was allocated as fmt_len+1 == 101 bytes; the read happens at fmt + fmt_len + 1 (one past the NUL). # Root Cause Analysis - file_checkfmt() parses fmt with a pointer p and repeatedly dereferences *p inside loops like: ``` while (strchr("#0.'+- ", *p) != NULL) p++; // funcs.c:103 if (*p == '*') p++; while (isdigit((unsigned char)*p)) p++; if (*p == '.') { p++; while (isdigit((unsigned char)*p)) p++; } while (strchr("hljztL", *p) != NULL) p++; // switch(*p) ... ``` - When fmt ends with '%' or otherwise forms an incomplete format, p can advance to point at the NUL terminator, and the parser keeps doing *p (and strchr(..., *p)) without checking bounds. - With a heap string of length N (bytes 0..N-1, N-1 is '\0'), the code evaluates *p when p == fmt+N (i.e., one past the allocated block), which is undefined behavior and trips ASan. # Suggested Fix In file_checkfmt(): - Carry the end pointer (fmt + strlen(fmt)) and guard every *p dereference: ``` const char *p = fmt, *end = fmt + strlen(fmt); while (p < end && (c = *p++) != '\0') { if (c != '%') continue; while (p < end && strchr("#0.'+- ", *p)) p++; if (p < end && *p == '*') p++; while (p < end && isdigit((unsigned char)*p)) p++; if (p < end && *p == '.') { p++; while (p < end && isdigit((unsigned char)*p)) p++; } while (p < end && strchr("hljztL", *p)) p++; if (p >= end) return 1; // incomplete/invalid format switch (*p) { ... } p++; } ``` - Alternatively, parse using a length-bounded approach (e.g., track remaining bytes) or bail out early on incomplete specifiers. | ||||
| Steps To Reproduce | # To Reproduce 1. Compile libmagic with AddressSanitizer: ``` export CC="clang -fsanitize=address,fuzzer-no-link -g " export CFLAGS="-g -O0" mkdir build_asan cd build_asan ../code/configure --prefix="$PWD/../bin_asan" --enable-static --disable-shared make -j$(nproc) make install ``` 2. Compile the Fuzz Driver: - Compile the fuzz driver with the following command: ``` clang -g -O0 ./fuzz_driver_168.c -o ./fuzz_driver_168 -fsanitize=fuzzer,address,undefined -I../bin_asan/include ../bin_asan/lib/libmagic.a -llzma -lzstd -lz -lbz2 ``` 3. Run the Fuzz Driver: ``` ./fuzz_driver_168 ./bug.input ``` # Desktop Environment - OS and Version: Linux (Ubuntu 24.04.2 LTS) - libmagic Version and Source: main branch at commit aa12ab73697390f59cb4f622d70464775e31c657 (Wed Oct 22 19:39:12 2025 +0000)(from git@github.com:file/file.git) - Compiler and Version: Ubuntu clang version 18.1.3 # ASan Report INFO: Running with entropic power schedule (0xFF, 100). INFO: Seed: 2463729613 INFO: Loaded 1 modules (5334 inline 8-bit counters): 5334 [0x62db43a262c0, 0x62db43a27796), INFO: Loaded 1 PC tables (5334 PCs): 5334 [0x62db43a27798,0x62db43a3c4f8), ./fuzz_driver_168: Running 1 inputs 1 time(s) each. Running: ./bug.input ================================================================= ==1938837==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x50b0000000a5 at pc 0x62db4396b8e6 bp 0x7ffe716f83b0 sp 0x7ffe716f83a8 READ of size 1 at 0x50b0000000a5 thread T0 #0 0x62db4396b8e5 in file_checkfmt /root/cov_fuzz/testTP/libmagic/build_asan/src/../../code/src/funcs.c:103:28 0000001 0x62db4396c3f2 in file_vprintf /root/cov_fuzz/testTP/libmagic/build_asan/src/../../code/src/funcs.c:142:6 0000002 0x62db4396cdad in file_printf /root/cov_fuzz/testTP/libmagic/build_asan/src/../../code/src/funcs.c:182:7 0000003 0x62db439325a0 in drive_libmagic_with_format /root/cov_fuzz/testTP/libmagic/file_checkfmt/./fuzz_driver_168.c:45:5 0000004 0x62db439319ea in LLVMFuzzerTestOneInput /root/cov_fuzz/testTP/libmagic/file_checkfmt/./fuzz_driver_168.c:77:5 0000005 0x62db4383efc4 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (/root/cov_fuzz/testTP/libmagic/file_checkfmt/fuzz_driver_168+0x7cfc4) (BuildId: 53bdc1f229f256d5de404ec5f23e6fb001f4e160) 0000006 0x62db438280f6 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) (/root/cov_fuzz/testTP/libmagic/file_checkfmt/fuzz_driver_168+0x660f6) (BuildId: 53bdc1f229f256d5de404ec5f23e6fb001f4e160) 0000007 0x62db4382dbaa in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/root/cov_fuzz/testTP/libmagic/file_checkfmt/fuzz_driver_168+0x6bbaa) (BuildId: 53bdc1f229f256d5de404ec5f23e6fb001f4e160) 0000008 0x62db43858366 in main (/root/cov_fuzz/testTP/libmagic/file_checkfmt/fuzz_driver_168+0x96366) (BuildId: 53bdc1f229f256d5de404ec5f23e6fb001f4e160) #9 0x79dbb18a91c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16 0000010 0x79dbb18a928a in __libc_start_main csu/../csu/libc-start.c:360:3 0000011 0x62db43822cc4 in _start (/root/cov_fuzz/testTP/libmagic/file_checkfmt/fuzz_driver_168+0x60cc4) (BuildId: 53bdc1f229f256d5de404ec5f23e6fb001f4e160) 0x50b0000000a5 is located 0 bytes after 101-byte region [0x50b000000040,0x50b0000000a5) allocated by thread T0 here: #0 0x62db438f30f3 in malloc (/root/cov_fuzz/testTP/libmagic/file_checkfmt/fuzz_driver_168+0x1310f3) (BuildId: 53bdc1f229f256d5de404ec5f23e6fb001f4e160) 0000001 0x62db43931c09 in build_format_string /root/cov_fuzz/testTP/libmagic/file_checkfmt/./fuzz_driver_168.c:22:25 0000002 0x62db43931894 in LLVMFuzzerTestOneInput /root/cov_fuzz/testTP/libmagic/file_checkfmt/./fuzz_driver_168.c:67:17 0000003 0x62db4383efc4 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (/root/cov_fuzz/testTP/libmagic/file_checkfmt/fuzz_driver_168+0x7cfc4) (BuildId: 53bdc1f229f256d5de404ec5f23e6fb001f4e160) 0000004 0x62db438280f6 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) (/root/cov_fuzz/testTP/libmagic/file_checkfmt/fuzz_driver_168+0x660f6) (BuildId: 53bdc1f229f256d5de404ec5f23e6fb001f4e160) 0000005 0x62db4382dbaa in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/root/cov_fuzz/testTP/libmagic/file_checkfmt/fuzz_driver_168+0x6bbaa) (BuildId: 53bdc1f229f256d5de404ec5f23e6fb001f4e160) 0000006 0x62db43858366 in main (/root/cov_fuzz/testTP/libmagic/file_checkfmt/fuzz_driver_168+0x96366) (BuildId: 53bdc1f229f256d5de404ec5f23e6fb001f4e160) 0000007 0x79dbb18a91c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16 0000008 0x79dbb18a928a in __libc_start_main csu/../csu/libc-start.c:360:3 #9 0x62db43822cc4 in _start (/root/cov_fuzz/testTP/libmagic/file_checkfmt/fuzz_driver_168+0x60cc4) (BuildId: 53bdc1f229f256d5de404ec5f23e6fb001f4e160) SUMMARY: AddressSanitizer: heap-buffer-overflow /root/cov_fuzz/testTP/libmagic/build_asan/src/../../code/src/funcs.c:103:28 in file_checkfmt Shadow bytes around the buggy address: 0x50affffffe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x50affffffe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x50afffffff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x50afffffff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x50b000000000: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00 =>0x50b000000080: 00 00 00 00[05]fa fa fa fa fa fa fa fa fa 00 00 0x50b000000100: 00 00 00 00 00 00 00 00 00 00 04 fa fa fa fa fa 0x50b000000180: fa fa fa fa fd fd fd fd fd fd fd fd fd fd fd fd 0x50b000000200: fd fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x50b000000280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x50b000000300: 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 ==1938837==ABORTING | ||||
| Tags | libmagic | ||||
| Date Modified | Username | Field | Change |
|---|---|---|---|
| 2025-10-27 03:20 | weijinjin | New Issue | |
| 2025-10-27 03:20 | weijinjin | Tag Attached: libmagic | |
| 2025-10-27 03:20 | weijinjin | File Added: file_checkfmt.zip | |
| 2025-11-04 20:42 | christos | Assigned To | => christos |
| 2025-11-04 20:42 | christos | Status | new => assigned |
| 2025-11-04 20:43 | christos | Status | assigned => resolved |
| 2025-11-04 20:43 | christos | Resolution | open => fixed |
| 2025-11-04 20:43 | christos | Fixed in Version | => HEAD |
| 2025-11-04 20:43 | christos | Note Added: 0004308 |