kobj_class_free() reclaims the compiled ops table with no grace period while the lock-free dispatch hot path reads it (force multiplier turning any consumer refcount slip into a freed function-pointer table call)
| Field | Value |
|---|---|
| ID | DF-2961 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H |
| CWE | CWE-362 / CWE-416 |
| File | sys/kern/subr_kobj.c |
| Lines | 179-208 (dispatch :184-188; kfree :206; publish :260) |
| Area | kern/kobj |
| Confidence | certain |
| Discovered | 2026-09-02 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | memcorrupt |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
Every KOBJOPLOOKUP dispatch executes kobj_lookup_method_cache() completely unsynchronized: it reads OPS->cls, cep[desc->id & 255], ce->desc and finally indirect-calls ce->func, and the slow path even WRITES the cache slot. Meanwhile kobj_class_uninstantiate() drops the last reference and kobj_class_free() immediately kfree()s the 2056-byte ops table holding only kobj_token β a lock the dispatch path never takes. kobj_init() likewise publishes obj->ops = cls->ops with no release/acquire discipline relative to a concurrent free. kobj therefore provides zero read-side lifetime protection: any consumer that drops the class reference even transiently while another CPU is mid-dispatch (exactly the unprivileged sysctl-reader-vs-device-teardown race already proven as DF-2679 against subr_bus) turns a would-be benign stale read into a UAF walk of freed slab memory culminating in an indirect call through reclaimed data β RIP control if the attacker grooms the freed 2056-byte block (/dev/dsp open/close cycles legitimately compile/free channel-class ops at attacker-influenced timing). No in-tree caller misbalances refs by itself β newbus, libiconv, and PCM sound are balanced β which is why this is filed Low/defense-in-depth: the kobj layer is the multiplier, the trigger is a consumer lifetime bug. No standalone PoC exists or should exist at the kobj layer (manufacturing the primitive requires a buggy demonstration driver; verification deferred to the consumer-side DF-2679 which owns the reachable trigger). Fix: grace period or never-free static classes (row sketch).
Timeline
- 2026-09-02 Discovered during pass-2 audit of subr_kobj.c (GLM 5.3).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2961 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 606 B | β raw | |
| VERDICT.md | β | 1.9 KB | β raw | |
| env.txt | β | 401 B | view raw | |
| manifest.json | β | 489 B | view raw | |
| verdict.json | β | 1.7 KB | view raw |
DF-2961 PoC evidence pack
What
kobj dispatch is unsynchronized against kobj_class_free (force multiplier for consumer refcount bugs).
Build
(none)
Run
(none)
Expected
static confirmation (unsynchronized dispatch vs immediate kfree; no standalone trigger)
Guest: DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:04 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 All logs are full untrimmed captures unless noted. fix.diff is the git-apply-able verified fix (authored after verification; never applied to the audit tree).
DF-2961 - verification verdict
Status: not_reproduced | reproduced: 0 | impact: corruption | confidence: certain
Confirmed as a force multiplier, not a standalone bug - matching the findings own framing. kobj_lookup_method_cache dispatch (subr_kobj.c:181-188 via KOBJOPLOOKUP) is fully unsynchronized (reads cls->ops, cache slot, ce->desc, indirect-calls ce->func; even WRITES the cache slot on miss at :169), while kobj_class_free kfrees the 2056-byte ops table holding only kobj_token (:206) - a lock the dispatch path never takes - and kobj_init publishes obj->ops with no ordering (:260). All in-tree consumers keep balanced references (subr_bus, libiconv, PCM sound), so no standalone trigger exists; the reachable trigger is consumer-side (DF-2679 family). Fix is architectural (grace period or never-free static classes); no patch compiled - documented per verification contract.
Evidence pointers
subr_kobj.c:181-188 unsynchronized dispatch vs :206 immediate kfree under kobj_token only
Kernel references
PoC changes vs the filing sketch
verification deferred to consumer-side DF-2679 per finding text
Fix
recommendation: grace period or never-free static classes
fix status: not_testable
architectural; no patch (documented)
Guest: DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:04 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
(No fix.diff for this item: documented recommendation only - see Fix above.)
Fix verification
not_testablearchitectural; no patch (documented)
Confirmed kernel references
Detail
Evidence (decisive lines)
subr_kobj.c:181-188 unsynchronized dispatch vs :206 immediate kfree under kobj_token only
PoC changes
verification deferred to consumer-side DF-2679 per finding text
Verified recommended fix
grace period or never-free static classes
Verdict
Confirmed as a force multiplier, not a standalone bug - matching the findings own framing. kobj_lookup_method_cache dispatch (subr_kobj.c:181-188 via KOBJOPLOOKUP) is fully unsynchronized (reads cls->ops, cache slot, ce->desc, indirect-calls ce->func; even WRITES the cache slot on miss at :169), while kobj_class_free kfrees the 2056-byte ops table holding only kobj_token (:206) - a lock the dispatch path never takes - and kobj_init publishes obj->ops with no ordering (:260). All in-tree consumers keep balanced references (subr_bus, libiconv, PCM sound), so no standalone trigger exists; the reachable trigger is consumer-side (DF-2679 family). Fix is architectural (grace period or never-free static classes); no patch compiled - documented per verification contract.
No comments yet.