# DF-2220 — VERDICT

**REPRODUCED — verbatim-code, kernel-compiled rig; leak quantified per cycle
with the kernel's own malloc accounting; fix A/B-validated in the same kernel.**
Impact: slow unbounded kernel memory growth (M_DRM) — availability only,
root-gated triggers. No privesc route (see below).

## Rig class and why

Same batch harness kernel as DF-2216 (`DFV22X`); the production
`sys/dev/drm/linux_kobject.c` is `#include`d **verbatim** (sha-gated
`50b635ee...`). `kobject_init_and_add`/`kobject_release` in the kernel are the
production functions. The `kvasprintf()` they call lives in the drm *module*
(`drm_dragonfly.c:67-84`), which cannot be loaded on this guest — so the rig
carries that body **verbatim** too (`kvasprintf_verbatim.c`, sha
`12efc1be...`, extracted+hashed by `build.sh`). The teardown chain
`kobject_put → kref_put → kobject_release` is the real header inline chain
(`linux/kobject.h:85-90`), not a re-implementation.

## Line-accurate confirmation

`linux_kobject.c:39`: `kobj->name = kvasprintf(M_WAITOK, fmt, ap)` — a
separately-malloc'd string (kmalloc `size+1`, M_DRM, drm_dragonfly.c:76).
`linux_kobject.c:45-51`: `kobject_release()` invokes `kobj->ktype->release(kobj)`
and nothing else — **`kobj->name` is never freed**. Upstream Linux frees it in
`kobject_cleanup()` (`kfree_const(kobj->name)` after the type release). The
in-tree release callbacks (`ttm_bo_global_kobj_release`,
`ttm_mem_zone_kobj_release`, `ttm_mem_global_kobj_release`,
`ttm_pool_kobj_release` ×2, `ttm_drm_class_device_release`) all `kfree()` the
container embedding the kobject, so the name pointer dies with the container on
every release. No UAF: the buffer is simply lost.

## Mechanical reproduction (decisive run, run.log)

| phase | result |
|---|---|
| CONTROL: 16384 paired `kmalloc(32)`/`kfree` | **delta = 0** |
| BASELINE: 16384 production-shape cycles (kmalloc container → verbatim `kobject_init_and_add` → real `kobject_put` chain → release kfrees container) | **delta = 655,360 B = 40 B/cycle**; `releases=16384` proves the release path ran |
| BASELINE single-shot (non-freeing release) | `init:+32 after_release:+32`, name content `[leaked_name_probe_0123456789]` read back intact **after** `kobject_release()` returned |
| FIXED single-shot | `init:+32 after_release_fixed:+0` |
| FIXED loop (patched release) | **delta = 0** |

40 = slab-chunk-quantized 33-byte name (`"rig_kobj_%06d_aaaa..."` → 32 chars +
NUL). Both return-shape and accounting prove the leak and its elimination.

## Fix validation (A/B, same kernel)

`fix.diff` saves `kobj->name` into a local **before** the release callback
(the callbacks free the container — saving after would be a UAF; this mirrors
upstream `kobject_cleanup` ordering exactly), then `kfree(name, M_DRM)` after
it, with `MALLOC_DECLARE(M_DRM)` added (the type is kernel-resident,
kern_slaballoc.c:202). Extracted-from-patched-bytes `kobject_release_fixed`
in the same kernel: loop delta 0, releases still 16384, identical container
lifetimes. `fix_status: fixed`.

## Reachability (who can actually trigger this)

In-tree creators: `ttm_bo.c:1565` (`ttm_bo_global_init`),
`ttm_memory.c:257/300/321` (memory zones + global),
`ttm_page_alloc.c:838`, `ttm_page_alloc_dma.c:1177` — ~5-7 kobjects per drm
subsystem lifecycle, each torn down via `kobject_put`. Trigger requires drm
module load/unload cycles (root `kldload`/`kldunload`) or GPU attach/detach
events: ~40 B per release, ~100-200 B per full lifecycle. Root-gated,
slow-growth availability issue — matches the filed Low severity
(AV:L/AC:H/PR:H).

## Privesc assessment (standing directive)

None. Pure allocation leak; the lost buffer is never re-accessed (no UAF
window), nothing is copied to userspace, no length or bounds is ever wrong.
No primitive → no escalation chain.

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