β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-2802

jail(2) id >= 100000 shares per-prison sysctl nodes (DF-0054 truncation at scale): silent loss of all per-jail capability controls + sysctl_oid freed-under-reference UAF at teardown

Field Value
ID DF-2802
Status new
Severity Medium
CVSS 3.1 CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:H/A:H
CWE CWE-416 (enabled by CWE-197 truncation)
File sys/kern/kern_jail.c
Lines 992-994 (name), 828β†’835 (teardown ordering), kern_sysctl.c:375-376/:446-455
Area kern
Confidence likely
Discovered 2026-08-31
Pass 2 (GLM 5.3 second pass)
Bucket memcorrupt
Reported pending
Known CVE none
CVE match novel

Summary

From the 100000th jail(2) since boot, prison ids are 6 digits but prison_sysctl_create names the sysctl node with ksnprintf(id_str, 6, ...) β€” every 10-id band shares one node name. sysctl_add_oid shares the existing node (oid_refcnt++) and all 11 per-jail capability leaf adds fail ("can't re-use a leaf"): the jail silently loses every per-jail control. The shared node is referenced by multiple sysctl_ctx_lists, and sysctl_ctx_free's dry run consumes an oid_refcnt it must not: each sharer teardown decrements twice but added once, so the sysctl_oid + children are kfree'd while other prisons still reference them β€” UAF reads/writes in M_SYSCTLOID on every later sharer teardown. Evidence: flood begins exactly at id 100000 (78,760 console lines by ~114k jails); ids proven strictly unique over 5000 logged assignments; serialized churn produces no flood (overlap required); 200k-iteration churn left the guest permanently wedged.

Threat model & preconditions

Host-root jail churn is routine (service jails, CI); after 100k cumulative creations every subsequent jail has broken capability management and each teardown operates on freed/reused sysctl oids β€” latent kernel heap corruption plus demonstrated whole-guest wedge.

Proof of contest

VERIFIED (findings/poc/DF-2802/): stock flood starts exactly at id 100000; 200k run wedged the guest permanently (24,896-line flood then silent kernel). Patched (#1 kernel with id_str[12] + dry-run guard + create-before-publish): 105k churn, ZERO flood lines, sysctl -a rc=0, guest healthy.

Render ids fully (id_str[12] + sizeof), stop the dry run from consuming references (if (del) oid_refcnt--), plus the DF-2799 create-before-publish ordering β€” diff in the pack.

References

  • DF-0054 (the underlying truncation, pass 1), DF-2799, DF-2737

Timeline

  • 2026-08-31 Discovered during pass-2 audit of kern_jail.c (GLM 5.3); at-scale flood + wedge reproduced + fix validated same run.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2802 Β· 10 files
FileTypeDescriptionSize
jailchurn.c β€” 2.5 KB view raw
jailtrace.c β€” 1.1 KB view raw
wedge_console.txt β€” 1.4 KB view raw
flood_evidence.txt β€” 406 B view raw
jids.log β€” 155.2 KB view raw
storm1_run.log β€” 4.0 KB view raw
run.patched.log β€” 172 B view raw
env.txt β€” 373 B view raw
fix.diff β€” 5.1 KB view raw
verdict.json β€” 5.5 KB view raw

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

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. nodes, guest healthy.

findings/poc/DF-2802/run.patched.log; fix.diff (kern_jail.c id_str + kern_sysctl.c dry-run guard + ordering)
↓ fix.diffDragonFly 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

Confirmed kernel references

Detail

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 (decisive lines)

['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']

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.

Verified 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.

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.