min() truncates 64-bit size_t to 32-bit u_int then stores in signed int: negative loop count unbounded buffer overrun
Summary
iconv_xlat_conv declares int n r (iconv_xlat.c:86) computes r=n=min(*inbytesleft *outbytesleft) (iconv_xlat.c:95). Kernel min is static __inline u_int min(u_int a u_int b) (sys/sys/libkern.h:76) so 64-bit size_t operands silently truncated to 32-bit u_int then stored in signed int. If truncated value has bit 31 set r becomes negative and while(r--) (iconv_xlat.c:98) runs ~2^32 iterations reading and writing far past both source and destination buffers. Sibling iconv_xlat16_conv avoids this by using size_t ir or and while(ir>0&&or>0). iconv_convmem(handle dst src int size) does inlen=outlen=size with no sign check so negative size becomes huge size_t whose low 32 bits exactly negative int bit pattern triggering runaway. Current smbfs/msdosfs callers pass small positive lengths no live trigger today. If any VFS/future caller passes length derived from attacker data without clamping becomes kernel memory-corruption primitive. [VERIFIED pass2: in out 0x80000000 gives INT_MIN loop count and 2 to the 31 unbounded stores Fatal trap 12 supervisor WRITE, 0x100000000 probe proves silent 64-to-32 truncation; size_t szmin fix-validated for the truncation with lying-caller counter behavior honestly noted as xlat16-parity by design]
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2248 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| run.log | β | 1.6 KB | view raw | |
| panic.txt | β | 374 B | view raw | |
| run_stock_boot.log | β | 14.7 KB | view raw | |
| run_controls_boot.log | β | 17.4 KB | view raw | |
| run_fixed_trigger_boot.txt | β | 835 B | view raw | |
| fix.diff | β | 481 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 | β | 202 B | view raw | |
| env.txt | β | 234 B | view raw |
Fix verification
not_testableControls bit-identical pre/post fix (16 x 0xAA). The stock-only silent-no-op probe semantics disappear (full count honoured). The lying-counter panic reproduces on BOTH kernels by design; the signedness/truncation defect is eliminated at the source level (size_t + szmin) but a panic-level A/B is not achievable without honest 2 GiB buffers -- recorded as not_testable, fix_failed is NOT claimed.
run.log stock probe vs controls; run_fixed_trigger_boot.txt same write fault at iconv_xlat_conv on the fixed kernel; source diff shows r/n now size_t via szmin.
Confirmed kernel references
Detail
Exploit chain
(latent) future caller deriving inbytesleft/outbytesleft from attacker data with bit-31-set values -> negative r -> sequential kernel heap overwrite of table-derived bytes -> classic corruption primitive; no such caller exists today.
Evidence (decisive lines)
panic.txt: Fatal trap 12 supervisor write at iconv_xlat_conv+0x63 after the TRIGGER printout. run.log: mode-2 probe ret=0 consumed=0 with ilen_left=4294967296 unchanged (truncation proof); 16-byte control all 0xAA on both kernels; run_fixed_trigger_boot.txt documents the honest fixed-kernel behavior.
PoC changes
Kernel-compiled harness driving the real KPI with oversized counters; added a dedicated 4 GiB truncation probe (mode 2) that proves the 64->32-bit loss without a crash.
Verified recommended fix
Use size_t counters with szmin() in iconv_xlat_conv (idiom parity with iconv_xlat16_conv) so the loop count can never go negative or lose the upper 32 bits.
Verdict
The negative-loop-count defect is mechanically proven: with in=out=0x80000000 the 32-bit u_int min result stored into int r is negative and while(r--) drives ~2^31 unbounded dst++ = d_table[src++] iterations off both buffers (Fatal trap 12, supervisor WRITE, iconv_xlat_conv+0x63); the 0x100000000 probe shows the 64->32-bit truncation as a silent 0-byte success. Latent (no in-tree caller passes derived lengths; smbfs/msdosfs pass small fixed sizes) -- requires an armed registrar plus a future caller trusting attacker sizes. The size_t/szmin fix removes the sign/truncation defect (controls identical, probe semantics change) but the lying-counter panic persists by design (caller-trust parity with iconv_xlat16_conv), so the panic-level A/B is classified not_testable rather than fixed.
No comments yet.