โฌข DragonFlyBSD Kernel Audit
DF-2802 / verdict.json
โ† back to finding โ†“ download raw
{
  "finding_id": "DF-2802",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "dos",
  "confidence": "likely",
  "verdict": "REPRODUCED (sharing primitive certain; teardown UAF code-certain; wedge demonstrated in combination). From the 100000th jail(2) since boot, prison ids are 6 digits but prison_sysctl_create formats the sysctl node name with ksnprintf(id_str, 6, ...) (kern_jail.c:992-994, DF-0054), so ids 100000-100009 all name their node '10000' etc. sysctl_add_oid shares the existing node (oid_refcnt++, kern_sysctl.c:446-455) and all 11 per-jail capability leaf adds fail ('can't re-use a leaf' x11) โ€” the prison silently loses every per-jail control. Evidence on stock: concurrent churn crossed id 100000 within ~90s and flooded the console with 78,760 leaf lines by ~114k jails; instrumented run proved ids 108261-113260 are strictly unique (0 duplicates), i.e. collisions are purely truncation; a serialized 5000-jail run (full teardown between creates) produced only +11 flood lines vs +5500 expected, proving sharing requires the create-vs-teardown overlap (prison_free releases jail_lock at kern_jail.c:828 BEFORE prison_sysctl_done at :835). The security-critical half: sysctl_ctx_free() dry-runs each ctx entry with del=0 but sysctl_remove_oid_locked() decrements oid_refcnt even in the dry run (kern_sysctl.c:375-376), so a shared node loses 2 references per sharer teardown while each sharer added 1 โ€” the sysctl_oid and its children list are kfree'd while up to 9 other prisons' pr_sysctl_tree/ctx entries still reference them (use-after-free reads/writes in M_SYSCTLOID on every later sharer teardown; allocator-reuse can smash live oids). A 200k-iteration churn left the guest permanently wedged (console flood then silence, ssh dead) โ€” attribution between this corruption and DF-2799's cycle could not be made unique in that run because it contained interactive sysctl -a reads; the post-quiesce tree of a 114k run happened to settle clean, so the UAF is stochastic rather than deterministic. On the patched kernel (id_str[12] full-length names + dry-run refcnt guard + create-before-publish ordering) a 105,000-iteration churn crossed id 100000 with ZERO flood lines, sysctl -a returned rc=0 and the guest stayed healthy.",
  "exploit_chain": "host-root jail churn (routine on jail-hosting/CI hosts over machine lifetime): cumulative jails >= 100000 -> every new jail shares its truncation-band node -> silent per-jail capability-management loss for all later jails + shared-node refcount undercount at teardown -> freed-under-reference sysctl_oid -> later sharer teardowns read/write freed M_SYSCTLOID memory (latent kernel heap corruption, root-gated trigger).",
  "evidence": [
    "findings/poc/DF-2802/flood_evidence.txt โ€” flood onset at id 100000 crossing, 78,760 total lines, jid uniqueness",
    "findings/poc/DF-2802/jids.log โ€” 5000 assigned jids, zero duplicates",
    "findings/poc/DF-2802/wedge_console.txt โ€” storm1 permanent wedge: 24,896-line console flood then silent kernel, ssh dead, QEMU alive",
    "findings/poc/DF-2802/storm1_run.log โ€” storm1 driver log",
    "findings/poc/DF-2802/run.patched.log โ€” fixed kernel: 105k churn, zero flood since reboot, sysctl -a rc=0, guest healthy"
  ],
  "kernel_refs": [
    "sys/kern/kern_jail.c:992", "sys/kern/kern_jail.c:994", "sys/kern/kern_jail.c:828",
    "sys/kern/kern_jail.c:835", "sys/kern/kern_jail.c:1065", "sys/kern/kern_sysctl.c:375",
    "sys/kern/kern_sysctl.c:446", "sys/kern/kern_sysctl.c:451", "sys/sys/jail.h:96"
  ],
  "poc_changes": "jailtrace.c added (serialized churn logging the kernel-assigned jid of every jail) to prove id uniqueness and the concurrency requirement; flood counting switched to timestamp-filtered /var/log/messages because the log persists across reboots.",
  "attempts": 2,
  "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": 2400,
  "guest_dirty": 0,
  "build_cmd": "cc -O2 -o jailchurn jailchurn.c && cc -O2 -o jailtrace jailtrace.c",
  "run_cmd": "./jailchurn 105000   (root; concurrent churn past id 100000; then grep 're-use a leaf' /var/log/messages | awk timestamp-filter | wc -l)",
  "code_hash": "57f95b906e9ee8c2b8a3312957ff50457d37dd9f29dfafaf948472586bca08f9",
  "notes": "Distinct from DF-0054 (pass-1 Low, the truncation itself): this is the multi-call security consequence โ€” sharing + dry-run refcount undercount + teardown UAF. Also observed: prison_sysctl_done ignores sysctl_ctx_free EBUSY and can leak a dead prison's tree (jail.10494.* nodes seen minutes after death). Fixed kernel = #1 Tue Sep 1 13:39:40 UTC 2026.",
  "recommended_fix": "id_str[12] with ksnprintf(id_str, sizeof(id_str), ...) so all ids < JAIL_MAX render uniquely; sysctl_remove_oid_locked must not consume oid_refcnt when del==0; create the prison tree before publishing the prison.",
  "fix_status": "fixed",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Tue Sep  1 13:39:40 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Patched kernel: 105,000-iteration churn crossing id 100000 produced zero 'can't re-use a leaf' lines (stock: 78,760 within ~90s of crossing), churn completed, sysctl -a rc=0, zero leftover jail.<id> nodes, guest healthy.",
  "fix_evidence": "findings/poc/DF-2802/run.patched.log; fix.diff (kern_jail.c id_str + kern_sysctl.c dry-run guard + ordering)"
}