DragonFlyBSD Kernel Audit
DF-2963 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-2963",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "data_integrity_aliasing",
  "confidence": "certain",
  "verdict": "Deterministic reproduction on the stock kernel: a crafted (or any legitimate 128-entry) GPT makes the kernel create /dev/adXs127 with a minor identical to /dev/adXs0 because dkmakeminor() drops slice bit 7 for slice index 128 (gptinit maps entry #127 to BASE_SLICE+126=128, beyond DKMAXSLICES-1=127). Two live devfs cdevs share one major:minor, and every read AND write through the s127 node silently addresses slice 0's data (proven: read returns entry-0 pattern; 512-byte write lands on abs LBA 34). No memory corruption (all dss_slices accesses bounds-checked), so no uid0 chain; impact is cross-partition data-integrity violation and device-identity confusion. fix.diff (bound created GPT slices to representable minor indices, entries #0..#126) rebuilt and validated on kernel #1: vn0s127 no longer created, s0/s126 unregressed, loud dmesg warning.",
  "exploit_chain": "crafted 128-entry GPT image -> vnconfig/boot-time auto-probe -> gptinit writes entry 127 into dss_slices[128] -> disk_probe creates 'vn0s127' with dkmakeminor(unit,128,255) == dkmakeminor(unit,0,255) -> dkslice() decodes 0 for the s127 cdev -> dscheck/dsopen/DIOCGPART all address dss_slices[0] (entry 0's extent) -> reads and writes through /dev/vn0s127 silently read/corrupt partition s0. Ceiling: cross-partition integrity violation + duplicate dev_t in devfs; no kernel memory corruption -> no uid=0.",
  "evidence": [
    "run.log: st_rdev vn0s0 == vn0s127 == 0x1e100807 with distinct st_ino (311 vs 321); vn0s127 reads 'ENTRY0ZONE_compat_slice0' while raw LBA 1000 holds 'ENTRY127ZONE_real_s127_data'; DIOCGPART vn0s127 reports offset 17408/30 blocks (slice 0's extent)",
    "run.3.log: 512-byte write via /dev/vn0s127 lands on abs LBA 34 (verified via /dev/vn0s0 and raw /dev/vn0); entry 127's true region untouched",
    "run.2.log: three reprobe attach/detach cycles survive - stable confusion, no panic",
    "run.fixed.log + dmesg: on fix.diff kernel #1 '/dev/vn0s127: No such file or directory', 'disk: GPT has 128 entries; only 127 slice devices representable, ignoring entry 127', vn0s0/vn0s126 correct",
    "fix.diff: bounds loop to MAX_GPT_SLICES (DKMAXSLICES-BASE_SLICE+1=127 entries)"
  ],
  "kernel_refs": [
    "sys/kern/subr_diskgpt.c:175",
    "sys/kern/subr_diskgpt.c:201-204",
    "sys/kern/subr_diskgpt.c:222",
    "sys/kern/subr_disk.c:448-451",
    "sys/sys/diskslice.h:107",
    "sys/sys/diskslice.h:252-253",
    "sys/sys/diskslice.h:327-333",
    "sys/kern/subr_diskslice.c:380-387",
    "sys/vfs/devfs/devfs_core.c:2439-2484",
    "sys/vfs/devfs/devfs_core.c:2571-2578"
  ],
  "poc_changes": "n/a (finding + PoC authored together in this run)",
  "attempts": 4,
  "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",
  "runtime_sec": 8,
  "guest_dirty": 0,
  "build_cmd": "sh /root/df2963/build.sh",
  "run_cmd": "sh /root/df2963/run.sh",
  "code_hash": "157d707e7b74241f9d1be387fbadf282857672299185c4a480073fa4750fab6d (gen_gpt.c), 68d5788d62cc095d34918678538d08242a1ff338485fc169fa6045092272f1ff (trigger.c)",
  "fix_status": "fixed",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Fri Sep  4 08:10:58 UTC 2026     root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC  x86_64",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "vm.sh reset with-src; fix.diff applied to guest /usr/src (patch -p0, both hunks clean); make -j6 nativekernel + make installkernel; reboot into kernel #1. Exact PoC re-run: /dev/vn0s127 not created, no duplicate st_rdev, vn0s0 (offset 17408/30, correct data) and vn0s126 (81920/2, correct data) unregressed, dmesg shows 'disk: GPT has 128 entries; only 127 slice devices representable, ignoring entry 127'. Bad behavior (aliasing + duplicate dev_t) fully absent.",
  "fix_evidence": "run.fixed.log; dmesg line quoted in VERDICT.md",
  "notes": "Also verified during fix validation that nil entries 119-125 are correctly rejected by the LBA checks (dmesg 'vn0 part NNN: bad lba_start=0 or lba_end=0'). The write-aliasing demo needs a full 512-byte payload: dscheck rejects sub-sector b_bcount with EINVAL (initial 15-byte dd attempt failed for that benign reason).",
  "recommended_fix": "Bound GPT slice-device creation to minor-representable indices: loop over min(entries, DKMAXSLICES-BASE_SLICE+1) in gptinit() and log when entry #127 is skipped (fix.diff)."
}