DF-5756 / verdict.json
{ "finding_id": "DF-5756", "status": "reproduced", "reproduced": true, "impact": "none", "confidence": "certain", "verdict": "iconv_ucs_conv() checks only the high-surrogate prefix (0xd8) on the leading UTF-16 unit (sys/libiconv/iconv_ucs.c:255) and has no surrogate check in the UTF-8 output stage (:293-312), so a lone low surrogate (U+DC00-DFFF) is accepted as an ordinary character and, given >=3 output bytes, re-encoded by ucs4_to_utf8 (:495-497, w=3) as CESU-8: the in-kernel witness converted \"\\xDC\\x00\" to ED B0 80 with ret=0 while rejecting a lone high surrogate (ret=-1) \u2014 the asymmetry. Via a crafted msdosfs KICONV image the unpaired surrogate surfaces as a silent ? substitution (dirent abcd?fghij.k) instead of an invalid-input rejection; base-system userland iconv(3) returns EILSEQ for the same bytes. No memory corruption, no leak: invalid-Unicode laundering/ parser-confusion input; severity Low. Fix (leading-DC rejection + output-stage surrogate check) rebuilt and validated: witness ret=-1 with nothing emitted, valid surrogate pairs byte-identical.", "exploit_chain": "crafted FAT image carries an LFN unit 0xDC00 -> msdosfs win2unixfn (msdosfs_conv.c:663-678, unit not 0 nor /) -> win2unixchr -> convchr(pm_w2u) -> iconv_ucs_conv: (*q&0xfc)==0xdc misses the 0xd8-only check (iconv_ucs.c:255) -> ucslen=2 (:264-266) -> UTF-8 second half has no surrogate check (:293-312) -> ucs4_to_utf8(0xDC00) wants w=3; with msdosfss 2-byte staging it fails dstlen<w (:505-506) -> win2unixchr substitutes ?; with a >=3-byte consumer buffer the CESU-8 bytes ED B0 80 are emitted (proven by cesu_witness.ko through the exported iconv_convchr API)", "evidence": "witness.log (shared with DF-5755: lowsur ret=0 out=ed b0 80; highsur-lone ret=-1); run.log (dirents abcd?fghij.k hex 61 62 63 64 3f ... and ?xyzabcd.k); logs/difftest.log (userland EILSEQ for \\xDC\\x00); logs/witness_patched.log (lowsur ret=-1 out untouched; valid-pair f0 9f 99 88 unchanged); run.patched.log", "kernel_refs": [ "sys/libiconv/iconv_ucs.c:255", "sys/libiconv/iconv_ucs.c:264", "sys/libiconv/iconv_ucs.c:271", "sys/libiconv/iconv_ucs.c:280", "sys/libiconv/iconv_ucs.c:293", "sys/libiconv/iconv_ucs.c:299", "sys/libiconv/iconv_ucs.c:495", "sys/libiconv/iconv_ucs.c:505", "sys/vfs/msdosfs/msdosfs_conv.c:633", "sys/vfs/msdosfs/msdosfs_conv.c:946" ], "poc_changes": "authored fresh; craft5756.c initially matched full-name UTF-16LE across the LFN entry \u2014 LFN entries split names between wePart1/wePart2 with 3 metadata bytes between, so patterns were fixed to per-part with an attr==0x0f cross-check; msdosfs dirmask and DF dirent (_DIRENT_DIRSIZ) quirks as in DF-5755", "attempts": 2, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 400, "guest_dirty": 0, "build_cmd": "cc -O0 -g -Wall -I/usr/src/sys -o kread5756 kread5756.c && cc -O0 -g -Wall -o craft5756 craft5756.c", "run_cmd": "sh run.sh (craft image, mount KICONV cs_local=cs_dos=UTF-8, readdir hex dump)", "code_hash": "2cf6a52d2cb307ff85db9f82d0eb2d416699d4bc1cc59cf68add820a3232b9a1", "notes": "CESU-8 bytes do not escape via msdosfs (2-byte staging); they do escape through any >=3-byte consumer of the same kernel converter (witness-proven). Upstream FreeBSD shares the defect. msdosfs-visible ? is identical pre/post fix by design \u2014 the fixs delta is at the converter level (reject vs launder).", "recommended_fix": "reject leading low surrogates at the input stage (extend the (*q & 0xfc) == 0xd8 check with a == 0xdc rejection) and reject code in [0xd800,0xe000) before ucs4_to_utf8 emission.", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "libiconv.ko rebuilt with fix.diff (stacked with DF-5755s) and reloaded; witness lowsur ret=-1 with output untouched (baseline: ret=0, ED B0 80); highsur-lone unchanged ret=-1; valid surrogate pair D83D DE48 still converts to f0 9f 99 88 (no regression); msdosfs readdir ?-substitution unchanged as designed (both accept-then-fail and reject-at-input surface as ? in win2unixchr \u2014 the converter now refuses invalid Unicode instead of laundering it).", "fix_evidence": "logs/witness_patched.log (shared DF-5755 pack); run.patched.log; logs/difftest_patched.log (userland unchanged)" } |