DF-5272 / verdict.json
{ "finding_id": "DF-5272", "status": "reproduced", "reproduced": true, "impact": "leak:20 bytes/call of uninitialized kernel heap to a privileged reader, unlimited calls; demonstrated disclosure of a live kernel sysctl OID name string fragment (...ilcd\\0spray_leak\\0) and of stable allocator/object remnants (ffffffff...01000000) across 64/64 ioctls x 3 rounds; evil-device-gated (same model as DF-0989, distinct path and mechanism: short transfer + device-inflated data_len clamp on a non-M_ZERO kmalloc).", "confidence": "certain", "verdict": "REPRODUCED on the stock guest kernel: the evil device completes READ SUBCHANNEL with a 4-byte short transfer while its returned header claims data_len=0x0014; cam_periph_runccb treats CAM_REQ_CMP with resid!=0 as success, cdioctl clamps the copyout length with the device-supplied data_len field (min(24, 0x14+4)=24) and copyouts 24 bytes of a kmallocd-without-M_ZERO buffer, so 20 uninitialized kernel-heap bytes reach userspace on every call. Plain rounds leaked the same stable kernel remnant 64/64 times; an in-process marker phase (alloc+mark+kfree of 24-byte chunks via sysctl between ioctls, no fork) leaked the kernels own sysctl OID name string spray_leak tail, proving live kernel heap data crosses the boundary. Patched kernel (M_ZERO) returns all-zero tail bytes on every run.", "exploit_chain": "evil CD-ROM + privileged open -> CDIOCREADSUBCHANNEL(data_len=24) -> kmalloc(24, M_SCSICD, M_WAITOK) without M_ZERO -> device DMAs only the 4-byte header with data_len=0x0014 -> success with resid=20 (unchecked) -> len=min(24, 20+4)=24 -> copyout of 20 uninitialized heap bytes -> repeatable heap-state sampling adjacent to the DF-5271 write primitive (reconnaissance).", "evidence": "run.log phase-1 lines [poc] 64/64 runs leaked non-zero heap bytes x3; phase-2 lines bytes[0..23] = 00110014ffffffff696c63640073707261795f6c65616b00 + uninit[4..23] = ....ilcd.spray_leak. (00 11 00 14 is the device header; the rest is leaked kernel heap containing the sysctl OID name string). Patched run: tail all-zero 64/64.", "kernel_refs": [ "sys/bus/cam/scsi/scsi_cd.c:2141", "sys/bus/cam/scsi/scsi_cd.c:2148-2158", "sys/bus/cam/scsi/scsi_cd.c:2163-2164", "sys/bus/cam/scsi/scsi_cd.c:2174-2176", "sys/bus/cam/scsi/scsi_cd.c:2178", "sys/bus/cam/cam_periph.c:962-963" ], "poc_changes": "Built the short-transfer response into the shared evilcd SIM (armed>=2: 4-byte DMA + data_len field 0x0014 + resid=dxfer-4). Marker phase needed allocator-aware rigs: freed-chunk markers must avoid bytes [0..7] (SLChunk.c_Next linkage - overwriting them caused a BADFREE2 panic, preserved in notes), and the marker spray must run in-process (sysctlbyname from the PoC between ioctls, no fork) because fork/exec noise reallocates the recycled chunk.", "attempts": 4, "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": 900, "guest_dirty": 0, "build_cmd": "cd /root/df5271 && make && cc -O -o poc_leak poc_leak.c (see build.sh)", "run_cmd": "sysctl debug.evilcd.armed=2; usched :1 ./poc_leak /dev/cd1; usched :1 ./poc_leak /dev/cd1 $(sysctl -n debug.evilcd.kptr) mark (see run.sh)", "code_hash": "sha256:poc_leak.c=ead09eebcf5fbad5", "notes": "Distinction from known DF-0989: that finding is cdreaddvdstructure() length-arithmetic underflow (DVDIOCREADSTRUCTURE); this is CDIOCREADSUBCHANNELs non-zeroed allocation + device-controlled copyout clamp + unchecked short transfer - different function, different mechanism, same ioctl family. cdreportkey() contrasts: it kmallocs with M_ZERO.", "recommended_fix": "kmalloc the subchannel buffer with M_ZERO (one-word fix; fix.diff validated together with DF-5271s fix in a single rebuilt kernel).", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Single-fix kernel (M_ZERO added, built+installed+rebooted together with DF-5271s fix): identical PoC returns bytes[4..23] all zero on every run (64/64), no heap remnants, no marker hits; guest stays up.", "fix_evidence": "run.patched.log / fix_run.log: zero tail bytes 64/64 on the patched kernel vs. leaked remnants 64/64 on baseline." } |