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

convchar==1 path converts one byte without checking inbytesleft/outbytesleft > 0: 1-byte OOB read/write

Summary

When called in single-character mode (convchar==1 via iconv_convchr/iconv_convchr_case iconv.c:290-301) iconv_xlat_conv unconditionally sets r=n=1 (iconv_xlat.c:92-93) without verifying at least one input byte and one output byte available. Subsequent loop (iconv_xlat.c:98-99) reads *src and writes *dst exactly once regardless. If *inbytesleft==0 reads one byte past source buffer. If *outbytesleft==0 writes one byte (value dp->d_table[oob_index] attacker-influenced via table contents) past destination buffer. Sibling iconv_xlat16_conv correctly bounds loop with while(ir>0&&or>0). Only in-tree convchr caller msdosfs dispatches to xlat16 not xlat. iconv_ucs calls convchr_case on _wctype handles normally xlat16. smbfs primary xlat consumer uses conv/convstr/convmem never convchr. Today effectively dead for xlat becomes live if future code calls iconv_convchr on xlat handle with empty buffer natural off-by-one in byte-at-a-time loop. 1-byte kernel heap write of table-derived value usable corruption primitive for slab grooming. [VERIFIED pass2: value-attributed - empty inbytesleft emits table Z 0x5c read past empty buffer, empty outbytesleft writes dst 0x51 to 0x41; dead path today, future 1-byte table-controlled heap write; empty-side guard fix-validated]

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2249 Β· 10 files
FileTypeDescriptionSize
run.log β€” 2.1 KB view raw
run_stock_boot.log β€” 17.4 KB view raw
run_fixed_boot.log β€” 16.3 KB view raw
fix.diff β€” 467 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 β€” 235 B view raw
env.txt β€” 234 B view raw

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

Stock: 0x5c emitted from an empty input buffer and 0x41 written over the canary with empty output. Fixed: ret=-1 on all trigger modes, canaries untouched, control conversion identical.

run.log stock vs fixed DF2249 sections.
↓ 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

Exploit chain

(latent, no current caller) future iconv_convchr caller on an xlat handle with an exhausted buffer -> 1-byte OOB read of heap slack feeding table lookup + 1-byte OOB write of a table-derived (registrar-influenced) value at dst -> slab-grooming grade primitive; not reachable today.

Evidence (decisive lines)

run.log: DF2249 mode1 out[0]=5c (table[Z]) with ilen=0; mode2 outbuf[8] 51 -> 5c-path values with olen=0; fixed section: ret=-1 and all-51 buffers.

PoC changes

Built the convchr canary harness with distinct table entries (A->41, Z->5c) so the OOB read is attributable by emitted value rather than by crash.

Verified recommended fix

Reject convchar==1 when inbytesleft==0 or outbytesleft==0 before setting r=n=1.

Verdict

The unconditional r=n=1 for convchar==1 is proven mechanically with value attribution: iconv_convchr on an xlat handle with inbytesleft=0 emits table[0x5A]=0x5c (a byte was read from a declared-empty buffer), and with outbytesleft=0 it writes table[A]=0x41 over a canary at dst (a byte written with no output space). No in-tree xlat convchr caller exists (msdosfs -> xlat16, smbfs -> conv/convmem), so the defect is dead code today and becomes a 1-byte table-controlled heap write only for a future byte-at-a-time caller. Fixed guard returns -1 and leaves both canaries untouched; control identical.