β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-2235

Read-before-bounds-check: 1-byte OOB read of source buffer in UCS-2/UTF-16 input parsing

Summary

In iconv_ucs_conv src-code-is-proper-subset-of-ENCODING_UNICODE branch converter unconditionally reads two bytes from source buffer (lines 248-253) before checking ir<inlen at line 267. When caller passes odd inbytesleft (ir==1) trivially produced by truncated UCS-2/UTF-16 string in crafted filesystem image or SMB packet this reads one byte past end of source buffer. Sibling iconv_xlat16 performs same operation safely via ir>1?*(src+1)&0xff:0 (iconv_xlat16.c:135). If OOB byte matches surrogate pattern 0xd8-0xdb AND UCS4+FROM_UTF16 set inlen becomes 4 function also reads src[2] and src[3] up to 3-byte overrun worst case. All overrun paths break with ret=-1 before any output written no info leak. Memory-safety/defense-in-depth defect. Trigger: crafted filesystem image (msdosfs long-name ntfs filename cd9660 Joliet UDF) with UCS-2/UTF-16 string odd length mounted by root with KICONV direct UCS-2LE/UTF-16BE cspair. Also via SMB client smb_copy_iconv processing server-supplied UCS-2 odd length. Practical impact low kernel heap slack rarely faults no data exfiltrated. Under KASAN definite report. [VERIFIED pass2: real UCS pair via kern.iconv.add faults exactly at iconv_ucs_conv+0x210 reading src 1 before the ir-lt-inlen check on a guard-page rig; silent 1-byte heap-slack read in real use, 3-byte speculation refuted; ir-lt-2 guard fix-validated]

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2235 Β· 12 files
FileTypeDescriptionSize
run.log β€” 731 B view raw
panic.txt β€” 386 B view raw
run_stock_boot.log β€” 13.6 KB view raw
run_controls_boot.log β€” 17.4 KB view raw
run_fixed_boot.log β€” 16.3 KB view raw
fix.diff β€” 321 B view raw
dfh.c β€” 24.3 KB view raw
dfarm.c β€” 2.4 KB view raw
harness.diff β€” 2.2 KB view raw
build.sh β€” 66 B view raw
run.sh β€” 193 B view raw
env.txt β€” 234 B view raw

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

Stock: Fatal trap 12 at the exact OOB read. Fixed (ir<2 guard): trigger returns ret=-1 consumed=0, guard page untouched, control conversion byte-identical (e4 89 81), guest healthy.

run_stock_boot.log vs run_fixed_boot.log (DF2235: TRIGGER SURVIVED ret=-1) and control output equality across both boots.
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #8 DFHARNESS x86_64 (stock-behavior kernels #4/#6, fixed #8; base 6.5-DEVELOPMENT 600519)

Confirmed kernel references

Detail

Evidence (decisive lines)

panic.txt and run_stock_boot.log: Fatal trap 12, supervisor read, fault VA = freed page, Stopped at iconv_ucs_conv+0x210 movzbl 0x1(%rbx),%edx. Controls in run_controls_boot.log (AB -> e4 89 81). Fixed boot: DF2235: TRIGGER SURVIVED ret=-1 consumed=0.

PoC changes

Replaced the sketch with a kernel-compiled harness: real kern.iconv.add registration of a UTF-8/UCS-2LE pair (converter name UTF-16BE, not UCS -- discovered via kern.iconv.drvlist) and a freed-successor-page guard so the kernel-mode fault is not silently re-faulted by vm_fault (a pmap_kremove-only guard failed because the vm_map entry remained).

Verified recommended fix

Check ir>=2 (and or>=2) before the unconditional 2-byte read in the proper-subset branch of iconv_ucs_conv.

Verdict

The 1-byte OOB read is proven mechanically: with a 1-byte UCS-2LE source placed at a page boundary and the following page freed, iconv_conv faults precisely at iconv_ucs_conv+0x210 (the q = (p+1) load at iconv_ucs.c:248) on the unmapped src[1] before the ir=4). Panic on the rig is guard-page instrumentation; practical severity stays Low/defense-in-depth.