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)
PoC verification
Evidence pack
findings/poc/DF-2249 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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
fixedStock: 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.
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.
No comments yet.