DF-2664 / verdict.json
{ "finding_id": "DF-2664", "status": "reproduced", "reproduced": 1, "impact": "none", "confidence": "certain", "verdict": "LZ4_decompress_safe() (sys/vfs/hammer2/hammer2_lz4.c:418, vendored upstream LZ4 r97) dereferences source[0] when inputSize==0 โ the pre-loop validation only rejects outputSize==0 (hammer2_lz4.c:407) โ a 1-byte OOB read past the declared input buffer that violates the documented 'never reads outside of input buffer' contract (hammer2_lz4.h:62-65). Unit-proven on the guest: SIGSEGV exactly at src with a guard page (run.log). In-kernel reachable from on-media compressed_size==0, which passes the strategy.c:199 KKASSERT even on INVARIANTS kernels (run_kernel.log: no panic, one 'READ PATH: Error during decompression', file reads as zeros). Impact fully contained by the caller: the speculative byte sits inside the dio allocation for every realistic layout, the decode always errors, and the output is zeroed โ no leak, no corruption. Negative proof: 2,000,000 guard-paged adversarial fuzz iterations with honest (inputSize,outputSize) โ 0 faults, 0 output-canary corruptions โ the r97 decoder is otherwise memory-safe on LP64; DF-0805's heap OOB is purely the caller's unchecked compressed_size. Guard (upstream r96 / v1.9.4 semantics) restored in fix.diff and validated at unit level: clean -1, no fault, fuzz still green.", "exploit_chain": "", "evidence": [ "run.log / run.2.log โ Test A: SIGSEGV at src (delta=0) inside LZ4_decompress_safe with inputSize==0; Test A2 control (inputSize=1 token=0x00 โ 0); Tests B+C: 2,000,000 iterations, 0 faults, 0 canary corruptions", "run_kernel.log โ on-media compressed_size=0 reaches LZ4_decompress_safe(inputSize=0) on stock INVARIANTS kernel: no panic, dmesg 'READ PATH: Error during decompression', unprivileged read returns zeros, guest healthy", "fix_run.log โ fixed build (r96/v1.9.4 guard): returns -1 cleanly, no fault, control + 2M fuzz green ('FIX VALIDATED')", "fix.diff โ one-line guard vs sys/vfs/hammer2/hammer2_lz4.c, git apply --check clean" ], "kernel_refs": [ "sys/vfs/hammer2/hammer2_lz4.c:418", "sys/vfs/hammer2/hammer2_lz4.c:407", "sys/vfs/hammer2/hammer2_lz4.h:62-65", "sys/vfs/hammer2/hammer2_strategy.c:198-205", "sys/vfs/hammer2/hammer2_strategy.c:206-216" ], "poc_changes": "Harness written fresh (verbatim decoder excerpt pattern reused from DF-0805's pack): guard-page token-fetch proof for inputSize==0, canonical 1-byte null-stream control, 2M-iteration guard-paged fuzz (random + all-0xFF/0x00 + structured tokens with offsets 0/1/2/7/8/0xFFFF + valid-shaped streams with flip/truncate mutations) with output canaries, plus in-kernel trigger adapted from DF-0805's hammer2_trigger.sh with compressed_size=0x00000000 instead of 0x7FFFFFFF.", "attempts": 3, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 240, "guest_dirty": 0, "build_cmd": "cc -O2 -Wall -Wextra -o lz4_zero_input_harness lz4_zero_input_harness.c", "run_cmd": "./lz4_zero_input_harness 1000000 (as maxx; kernel stage: sh hammer2_trigger.sh as root, read as maxx)", "code_hash": "7b935bbf7b3c66f8ce9595df3a317fe6878aaf7747f4e3ca8026f452b5fb745d", "notes": "Upstream provenance pinned by diffing the in-tree function against historical upstream: verbatim LZ4 r97 (svn trunk@97, 2013-06-10). The inputSize==0 guard existed in r96, was dropped by the r97 de-genericization, and is present again in v1.9.4 (lib/lz4.c:1983) โ the vendored copy sits exactly in the unguarded window. CVE-2014-4610 (LZ4 < r118) is 32-bit-only and structurally unreachable here (sys/platform = pc64, vkernel64 only). Fix validation was performed at unit level deliberately: in-kernel baseline vs fixed behavior is observationally identical (silent 1-byte speculative read; both produce dmesg error + zeroed output), so a kernel rebuild cannot distinguish them โ the unit harness is the only level with an observable delta.", "recommended_fix": "Restore the upstream r96/v1.9.4 guard: reject inputSize==0 before the main loop ('if ((endOnInput) && unlikely(inputSize==0)) goto _output_error;' after the outputSize==0 check).", "fix_status": "fixed", "fix_kernel_uname": "", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Unit-level validation (verbatim decoder copy): baseline faults at src with inputSize==0; patched build returns -1 with no fault and stays green across the full 2M-iteration fuzz and the null-stream control (fix_run.log). Kernel-level rebuild validation intentionally not performed: the in-kernel bad behavior has no observable runtime delta vs fixed (both yield the dmesg error and zeroed output), so only the unit level can demonstrate the fix; fix.diff is git-apply clean against sys/vfs/hammer2/hammer2_lz4.c.", "fix_evidence": [ "fix_run.log โ 'FIX VALIDATED - inputSize==0 cleanly rejected, no OOB read', control + 2M fuzz green", "fix.diff โ git apply --check clean vs sys/vfs/hammer2/hammer2_lz4.c", "upstream corroboration: r96 lz4_decoder.h guard; v1.9.4 lib/lz4.c:1983 'if (unlikely(srcSize==0)) { return -1; }'" ] } |