Skip to content

[Bug]Netpbm decoder accepts width=0 → NULL+0 pointer arithmetic on empty pixel row in swizzle_hard (CWE-476) #178

Description

@1820893135-pixel

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:

P6\n0 2\n65535\n

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

  1. 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").
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions