DragonFlyBSD Kernel Audit
← triage · dashboard
DF-2236

iconv_xlat16_open trusts attacker-controlled cp_data size causing OOB heap read and NULL-deref panic

Summary

iconv_xlat16_open() interprets csp->cp_data as [0x200-pointer index][variable count of 0x80-uint32_t data blocks] structure but never validates cp_data is non-NULL or large enough to contain index and all data blocks implied by non-NULL index entries. Pointer headp advanced by 0x80 uint32_t (0x200 bytes) per non-NULL entry with no bounds check against actual allocation. kern.iconv.add sysctl has NO privilege check only enforces ia_datalen<=ICONV_CSMAXDATALEN not minimum. Any local user can register maliciously undersized xlat16 table. When filesystem mounted using charset iconv_xlat16_open sets d_table[i] to wild pointers past allocation. Every dp->d_table[c1][c2] lookup in iconv_xlat16_conv/tolower/toupper reads OOB kernel heap leaking data back to userspace as translated text. Deterministic kernel heap info leak of up to ~256KB per malicious table leaking adjacent slab allocations kernel pointers KASLR defeat credentials network data. ia_datalen=0 variant cp_data==NULL causes immediate NULL-deref panic at line 71. struct iconv_cspair has no cp_datalen field converter has no size information.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2236 · 8 files
FileTypeDescriptionSize
df2236_register.c trigger-source registers malicious xlat16 pair with cp_data=NULL via sysctl 1.9 KB view raw
build.sh build-script builds df2236_register 137 B view raw
run.sh run-script register malicious pair + mount_msdos -D CP437 -> panic 1.5 KB view raw
VERDICT.md verdict analysis: mechanism, privilege correction, fix 4.0 KB ↓ raw
fix.diff suggested-fix add cp_datalen + validate cp_data bounds in iconv_xlat16_open 2.1 KB view raw
fix_build.log build-log libiconv.ko rebuild with fix (rc=0) 9.1 KB view raw
panic.txt panic-signature fatal trap 12 in iconv_xlat16_open+0x34 663 B view raw
env.txt environment uname, cc, modules, privilege note 316 B view raw
VERDICT.md verdict analysis: mechanism, privilege correction, fix
↓ download raw

DF-2236 — iconv_xlat16_open trusts attacker-controlled cp_data (NULL deref + OOB read)

Verdict

REPRODUCED — kernel panic (fatal trap 12, NULL pointer dereference) in iconv_xlat16_open confirmed on the unpatched GENERIC kernel. Fix validated: the patched libiconv.ko rejects the malicious table with EINVAL instead of panicking.

Bug analysis

iconv_xlat16_open() (sys/libiconv/iconv_xlat16.c:59-93) interprets csp->cp_data as a [0x200-entry index of uint32_t* pointers][variable count of 0x80-uint32_t data blocks] layout, but never validates that cp_data is non-NULL or large enough:

headp = (uint32_t *)((caddr_t)csp->cp_data + sizeof(dp->d_table));  // cp_data + 0x1000
idxp = (uint32_t **)csp->cp_data;                                   // cp_data (may be NULL!)
for (i = 0; i < 0x200; i++) {
    if (*idxp) {                  // dereferences cp_data — NULL => panic
        dp->d_table[i] = headp;
        headp += 0x80;            // advances 0x200 bytes per non-NULL entry, unchecked
    }
    idxp++;
}
  • CWE-476 NULL deref: if ia_datalen=0, cp_data stays NULL → *idxp dereferences NULL → fatal trap 12 page fault. This is the demonstrated crash.
  • CWE-125 OOB read: if ia_datalen is non-zero but undersized, idxp reads past cp_data (OOB index entries) and headp advances past cp_data (OOB data pointers). Later, iconv_xlat16_conv() dereferences dp->d_table[c1][c2] → OOB heap read.

Reproduction

  1. Register a malicious xlat16 charset pair "CP437"→"ISO8859-1" with ia_datalen=0 (→ cp_data=NULL) via kern.iconv.add sysctl.
  2. Create a 32 MB FAT16 image (newfs_msdos -F 16).
  3. mount_msdos -D CP437 /dev/vn0 /mnt → kernel msdosfs_mount calls iconv_open("CP437","ISO8859-1") → finds the malicious pair → iconv_xlat16_open*NULLpanic.
Fatal trap 12: page fault while in kernel mode
fault virtual address     = 0x0
fault code                = supervisor read data, page not present
Stopped at      iconv_xlat16_open+0x34:  movq (%rsi,%rax,1),%rdx

The mount_msdos userland registration is not a barrier: kiconv_add_xlat16_cspair() returns 0 (success) when the pair already exists (kiconv_lookupcs() hit), so the pre-registered malicious pair is the one iconv_open finds.

Privilege correction (finding claim error)

The finding claims "kern.iconv.add sysctl has NO privilege check" and rates this PR:L (unprivileged). This is incorrect: on DragonFly 6.5-DEVELOPMENT, writing to kern.iconv.add requires root — an unprivileged user gets EPERM:

$ ./df2236_register CP437 ISO8859-1   # as maxx (uid 1001)
sysctl kern.iconv.add failed: Operation not permitted

The SYSCTL_PROC node uses CTLFLAG_RW without CTLFLAG_ANYBODY, so the kernel's sysctl write path enforces suser_cred. The real privilege is PR:H (root-only), making this a root→kernel DoS, not an unprivileged→kernel exploit. The code bug (NULL deref / OOB) is real, but the severity is lower than claimed.

Fix

Three-part fix (in fix.diff):

  1. sys/sys/iconv.h: add int cp_datalen to struct iconv_cspair to track the actual allocation size.
  2. sys/libiconv/iconv.c: set cp_datalen in iconv_register_cspair (default 0) and in iconv_sysctl_add (store ia_datalen).
  3. sys/libiconv/iconv_xlat16.c: validate cp_data != NULL and cp_datalen >= sizeof(dp->d_table) before use; check that each implied 0x200-byte data block stays within cp_data + cp_datalen before assigning dp->d_table[i].

Fix validation

Kernel PoC result
Unpatched (#0 baseline) fatal trap 12 panic in iconv_xlat16_open+0x34 (NULL deref)
Patched libiconv.ko mount returns EINVAL gracefully, no panic, guest alive
Patched + normal mount (no charset) mount succeeds (exit 0) — fix doesn't break normal operation

Rebuilt libiconv.ko with make KERNCONF=X86_64_GENERIC in sys/libiconv/ (rc=0), installed to /boot/kernel/libiconv.ko, reloaded.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED: unpatched libiconv.ko -- same PoC (register cp_data=NULL + mount_msdos -D CP437) -> fatal trap 12 panic in iconv_xlat16_open+0x34. Patched libiconv.ko -- same PoC -> mount returns EINVAL gracefully (iconv_xlat16_open detects NULL cp_data, returns EINVAL), NO panic, guest alive. Normal mount without charset succeeds (exit 0) -> fix doesn't break operation.

baseline: fatal trap 12 page fault at iconv_xlat16_open+0x34, fault addr=0x0, guest DOWN. patched: mount_msdos -D CP437 -> 'Invalid argument' (EINVAL from iconv_xlat16_open), guest UP. normal mount_msdos (no -D): exit 0 on patched kernel.
↓ fix.diffDragonFly 6.5-DEVELOPMENT (libiconv.ko rebuilt with fix, loaded as module)

Confirmed kernel references

Detail

Exploit chain

none (not memory corruption in write/UAF sense -- NULL-pointer-dereference DoS). Root registers malicious xlat16 pair with cp_data=NULL via kern.iconv.add sysctl, then mount_msdos -D triggers iconv_open -> iconv_xlat16_open -> NULL deref -> kernel panic (fatal trap 12). Immediate deterministic DoS. No escalation chain possible (NULL deref crashes instantly).

Evidence (decisive lines)

Fatal trap 12: page fault while in kernel mode | fault virtual address = 0x0 | fault code = supervisor read data, page not present | Stopped at iconv_xlat16_open+0x34: movq (%rsi,%rax,1),%rdx | db>

PoC changes

Created df2236_register.c (registers malicious xlat16 pair with ia_datalen=0 via kern.iconv.add sysctl), build.sh, run.sh. Finding's PoC scaffold did not exist; built from scratch.

Verified recommended fix

Three-part fix: (1) add int cp_datalen to struct iconv_cspair (sys/sys/iconv.h) to track allocation size; (2) set cp_datalen in iconv_register_cspair and iconv_sysctl_add; (3) in iconv_xlat16_open validate cp_data != NULL and cp_datalen >= sizeof(dp->d_table), and check each implied 0x200-byte data block stays within cp_data+cp_datalen before assigning dp->d_table[i]. Supersedes finding proposal (finding did not propose concrete fix). Full diff in findings/poc/DF-2236/fix.diff.

Verdict

REPRODUCED. iconv_xlat16_open (iconv_xlat16.c:68-78) dereferences csp->cp_data without NULL check. Registering charset pair with ia_datalen=0 via kern.iconv.add leaves cp_data=NULL; when iconv_open later finds this pair (triggered by mount_msdos -D CP437), iconv_xlat16_open executes *idxp where idxp=NULL -> fatal trap 12 page fault at iconv_xlat16_open+0x34 (fault virtual address 0x0). Confirmed by panic in boot.log. PRIVILEGE CORRECTION: finding claims kern.iconv.add has 'NO privilege check' (PR:L) but this is FALSE -- sysctl returns EPERM for unprivileged users (verified as maxx uid 1001). Real privilege PR:H (root-only), making this root->kernel DoS, not unprivileged->kernel.