Summary
Wuffs' Netpbm decoder (decode_netpbm.wuffs) only bounds width/height/max_value to 24-bit ranges and never rejects 0. A P6/PPM file with width=0 is therefore treated as a legitimate image. For a P6 file with maxval 65535 the decoder uses BGRA_NONPREMUL_4X16LE as the source pixel format, and swizzle_hard computes the pointer arithmetic for an empty pixel row: wuffs_base__slice_u8__subslice_i (release/c/wuffs-unsupported-snapshot.c:1920) evaluates s.ptr + 0 where s.ptr is NULL — NULL pointer arithmetic, undefined behavior. The UBSan pointer-overflow check traps with SIGILL (exit 132) in sanitizer builds.
- Affected versions: wuffs HEAD
9d8285b
- Severity: Low (UB / DoS in sanitizer builds; no memory corruption in release builds)
- CWE: CWE-476 (NULL Pointer Dereference)
- Sanitizer: UBSan pointer-overflow / null-pointer-arithmetic
Detail
Affected code
/* release/c/wuffs-unsupported-snapshot.c:1920 (excerpt), wuffs_base__slice_u8__subslice_i */
wuffs_base__slice_u8 wuffs_base__slice_u8__subslice_i(wuffs_base__slice_u8 s, uint64_t i) {
...
return wuffs_base__make_slice_u8(s.ptr + i, ...); /* <-- s.ptr is NULL, i==0: NULL+0 is UB */
}
called from wuffs_netpbm__decoder__swizzle_hard at :67152 (DSL: std/netpbm/decode_netpbm.wuffs swizzle_hard).
Root cause: do_decode_frame_config in decode_netpbm.wuffs clamps width/height/max_value to 24-bit ranges but allows width==0. The subsequent swizzle of an empty pixel row performs NULL + 0, which the C standard leaves undefined.
Reachability: any P6/PPM file with maxval 65535 and width 0 (as small as 21 bytes) is accepted and crashes sanitizer-instrumented processes.
Call chain:
wuffs_base__slice_u8__subslice_i (wuffs-unsupported-snapshot.c:1920, NULL+0)
└─ wuffs_netpbm__decoder__swizzle_hard (:67152)
└─ wuffs_netpbm__decoder__decode_image
└─ LLVMFuzzerTestOneInput (harness.c)
POC (tested on the unmodified source)
Trigger 1 — 21-byte P6/PPM, width=0, height=5, maxval=65535 — attached as wuffs_poc1_width0_21b.pnm:
P6\n0 \n5 \n\t\n\n65535\n
followed by 26 raw 16-bit pixel bytes (binary):
12 34 56 00 00 06 08 09 f7 09 09 09 09 09 09 09 09 16 32 35 35 e9 00 58
/tmp/vulnpilot_v2/tasks/162b7ff8595849d3/build/out/wuffs_netpbm_fuzzer wuffs_poc1_width0_21b.pnm
Trigger 2 — 26-byte P6/PPM, width=0, height=2, maxval=65535 — attached as wuffs_poc2_width0_26b.pnm:
then raw bytes:
10 00 7f 00 30 00 10 01 20 01 30 01 10
Trigger result
UBSAN: pointer-overflow / null-pointer-arithmetic at
release/c/wuffs-unsupported-snapshot.c:1920 (wuffs_base__slice_u8__subslice_i)
called from wuffs_netpbm__decoder__swizzle_hard :67152
Illegal instruction (core dumped)
EXIT=132 (SIGILL, ud1 trap)
- Replay exit code:
132 (SIGILL, trap-mode UBSAN)
- Deterministic: yes — 7 crash files from the AFL run, all width=0 P6/PPM with maxval 65535 (21B/26B/47B/45B/41B/135B/93B variants), all the same
NULL+0 root cause
- No memory corruption in normal release builds; pure UBSan-flagged UB remains, so the practical impact is DoS in sanitizer/QA/debug environments
Suggested fix
- On the decoder side: explicitly reject
width==0 / height==0 after the header parse in do_decode_frame_config (return an error status such as "#unsupported Netpbm file").
- Additionally harden the base helper
wuffs_base__slice_u8__subslice_i so that when s.ptr is NULL it returns the empty slice without performing NULL + i arithmetic.
Summary
Wuffs' Netpbm decoder (
decode_netpbm.wuffs) only boundswidth/height/max_valueto 24-bit ranges and never rejects 0. A P6/PPM file withwidth=0is therefore treated as a legitimate image. For a P6 file withmaxval 65535the decoder usesBGRA_NONPREMUL_4X16LEas the source pixel format, andswizzle_hardcomputes the pointer arithmetic for an empty pixel row:wuffs_base__slice_u8__subslice_i(release/c/wuffs-unsupported-snapshot.c:1920) evaluatess.ptr + 0wheres.ptris NULL — NULL pointer arithmetic, undefined behavior. The UBSan pointer-overflow check traps with SIGILL (exit 132) in sanitizer builds.9d8285bDetail
Affected code
called from
wuffs_netpbm__decoder__swizzle_hardat:67152(DSL:std/netpbm/decode_netpbm.wuffsswizzle_hard).Root cause:
do_decode_frame_configindecode_netpbm.wuffsclampswidth/height/max_valueto 24-bit ranges but allowswidth==0. The subsequent swizzle of an empty pixel row performsNULL + 0, which the C standard leaves undefined.Reachability: any P6/PPM file with
maxval 65535andwidth 0(as small as 21 bytes) is accepted and crashes sanitizer-instrumented processes.Call chain:
POC (tested on the unmodified source)
Trigger 1 — 21-byte P6/PPM, width=0, height=5, maxval=65535 — attached as
wuffs_poc1_width0_21b.pnm:followed by 26 raw 16-bit pixel bytes (binary):
Trigger 2 — 26-byte P6/PPM, width=0, height=2, maxval=65535 — attached as
wuffs_poc2_width0_26b.pnm:then raw bytes:
Trigger result
132(SIGILL, trap-mode UBSAN)NULL+0root causeSuggested fix
width==0/height==0after the header parse indo_decode_frame_config(return an error status such as"#unsupported Netpbm file").wuffs_base__slice_u8__subslice_iso that whens.ptris NULL it returns the empty slice without performingNULL + iarithmetic.