DF-5554 / verdict.json
{ "finding_id": "DF-5554", "status": "reproduced", "reproduced": true, "impact": "none", "confidence": "certain", "verdict": "cam_calc_geometry() (sys/bus/cam/cam.c:375-400) keeps all arithmetic in uint32_t locals while ccg->volume_size is u_int64_t (cam_ccb.h:779): size_mb = volume_size / size_mb (cam.c:389) and cylinders = volume_size / secs_per_cylinder (cam.c:398) both wrap mod 2^32 for volumes >= 2^32 cylinders-worth of sectors. volume_size is filled straight from device-reported READ CAPACITY(16) (scsi_da.c:1851 scsi_8btou64 -> dasetgeom scsi_da.c:2303), so any crafted SCSI target (USB gadget, iSCSI/NBD export, virtio-scsi backend) controls it fully. KLD harness on the PRODUCTION kernel function shows: 2^43 sectors -> H=64 S=32 C=0 (extended-translation decision flips AND cylinders wraps to 0; truth H=255 S=63 C=547531467), 2^32*16065 -> C=0, max-u64 -> C=1500097198 (arbitrary modular value). Fixed kernel (fix.diff: 64-bit math + UINT32_MAX saturation) returns the exact true geometry for the 2^43 case and saturates unrepresentable quotients; guards unchanged. Logic bug only: the geometry is advisory metadata (disk_info.d_ncylinders, dmesg announce), in-kernel consumers guard secpercyl!=0 and size from 64-bit d_media_blocks -- no OOB, no leak, no panic. Low severity, filed for correctness and because the same crafted-capacity path feeds sharper consumers elsewhere.", "exploit_chain": "", "evidence": "run.log (baseline, stock kernel #0): DF5554[2^43 sectors...] ker: H=64 S=32 C=0 vs tru: H=255 S=63 C=547531467; DF5554[max-u64...] ker: C=1500097198 vs true 1148256711715502; DF5554[2^32*16065...] ker: C=0. run.patched.log (kernel #1 with fix.diff): 2^43 case ker: H=255 S=63 C=547531467 ... ok (matches 64-bit math); wrap cases saturate at C=4294967295 instead of wrapping. fix_build.log: >>> Kernel build for X86_64_GENERIC completed. VERDICT.md has the full narrative and the reachability chain.", "kernel_refs": [ "sys/bus/cam/cam.c:375-400", "sys/bus/cam/cam.c:378", "sys/bus/cam/cam.c:389", "sys/bus/cam/cam.c:398", "sys/bus/cam/cam_ccb.h:776-783", "sys/bus/cam/scsi/scsi_da.c:1849-1853", "sys/bus/cam/scsi/scsi_da.c:2284-2322", "sys/dev/disk/nata/atapi-cam.c:470", "sys/dev/virtual/virtio/scsi/virtio_scsi.c:801-802", "sys/bus/u4b/storage/umass.c:2480-2482" ], "poc_changes": "harness authored fresh (calcgeo.c KLD; no seed existed). One iteration: cam.h needs a stub opt_cam.h for standalone kmod.mk builds (-- fixed in build.sh). Baseline dmesg captured before the with-src reset; fix leg used make nativekernel (buildkernel refused: no buildworld in snapshot).", "attempts": 3, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (baseline) / #1: Wed Oct 7 13:21:14 UTC 2026 (fix-validation build)", "runtime_sec": 1980, "guest_dirty": 0, "build_cmd": "sh build.sh (guest /root/df5554: KLD df5554.ko via /usr/src/sys/conf/kmod.mk; fix leg: cd /usr/src && patch -p1 < fix.diff && make -j4 nativekernel KERNCONF=X86_64_GENERIC && make installkernel)", "run_cmd": "kldload ./calcgeo/df5554.ko; dmesg | grep DF5554 (same on baseline and patched kernels)", "code_hash": "c45006ae084dbeae7861f631a22765b71291e929e291634e4fdf1168f2980c9d", "notes": "Reserved block DF-5554..DF-5566: only DF-5554 used -- this pass-2 audit of sys/bus/cam/cam.c yielded exactly one finding (Low, logic). Everything else hunted was killed: cam_strvis writes at most dstlen bytes (case analysis: 4-byte octal escape requires dstlen>4, NUL always lands in the last reserved slot); cam_strmatch reads *str only while str_len>0 and patterns are NUL-terminated kernel constants; cam_error_string uses fixed-length sbuf (flags=0, no autoextend) and xpt_path_string is sbuf-bounded to 64; kprintf %s NULL is handled ((null), sys/kern/subr_prf.c:753) so cam_error_prints possible NULL return cannot deref; cam_status_table is strictly ascending (0x00..0x1b, 0x1d, 0x33..0x3f) so bsearch is correct, 0x1c simply unmatched; the block_size==0 division guard works (status CAM_REQ_CMP_ERR). No module event handler or sysctl handlers live in this file (cam_module_event_handler is cam_xpt.c:1371; the only sysctl is the childless CTLFLAG_RD SYSCTL_NODE at cam.c:108).", "recommended_fix": "Do the geometry math in uint64_t and saturate cylinders to UINT32_MAX instead of silently wrapping (see fix.diff); longer term follow FreeBSD and retire XPT_CALC_GEOMETRY.", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff applied to guest /usr/src, X86_64_GENERIC built with make nativekernel (fix_build.log: >>> Kernel build for X86_64_GENERIC completed, rc=0), installed, rebooted into kernel #1 (13:21:14 UTC). Exact same harness re-run: the 2^43-sector case now returns the exact true geometry (H=255 S=63 C=547531467, ok (matches 64-bit math)) instead of H=64 S=32 C=0; quotients that cannot fit u32 saturate at C=4294967295 instead of wrapping to 0/arbitrary values; sane-disk, zero-volume and block_size=0 guard outputs are byte-identical to baseline. Both baseline mis-behaviors (geometry flip, cylinder wrap) are GONE.", "fix_evidence": "run.patched.log vs run.log (decisive lines: ker: H=255 S=63 C=547531467 ... ok vs baseline ker: H=64 S=32 C=0); fix_build.log; fix.diff; VERDICT.md Fix validation section" } |