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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2248 Β· 12 files
FileTypeDescriptionSize
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_testable
baseline reproduced→ patch + rebuild →patched reproduced

Controls 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.
↓ 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) 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.