# DF-5052 — VERDICT

**Finding**: `dal_vector_destruct()` (vector.c:131-137) frees `container` and
resets `count`/`capacity` to zero, but leaves `container` pointing at the
freed chunk (contrast `dal_vector_destroy`, vector.c:139-147, which NULLs the
outer pointer). Any second `dal_vector_destruct()` on the same vector — or any
later `dal_vector_reserve()`/`dal_vector_at_index()` on it — operates on freed
memory. `dal_vector_reserve` in particular would `krealloc()` the dangling
pointer (vector.c:293) and then write through it.

**Source analysis** (pass 2): the two in-tree destruct callers
(`dal_ddc_i2c_payloads_destroy` / `dal_ddc_aux_payloads_destroy`,
dc_link_ddc.c:160-168, 197-205) each destruct once and NULL their wrapper —
no in-tree double-destruct exists, so like DF-5051 this is a latent library
defect (the exported API cannot defend itself because destruct does not clear
the one field that makes re-use dangerous).

**Runtime verification** (same guest/facility as DF-5051):

1. `dal_vector_create(NULL, 4, 16)` → valid vector
   (`container=0xfffff80116c07ac0, struct_size=16, count=0, capacity=4`).
2. `dal_vector_destruct(V)` — bcopy-read of the vector afterwards shows
   **`container` still 0xfffff80116c07ac0 (dangling), count=0, capacity=0**
   — the exact unfixed state from the source.
3. `dal_vector_destruct(V)` again:

```
panic: memory chunk 0xfffff80116c07ac0 is already free!
Trace: chunk_mark_free() at chunk_mark_free+0xae
       _kfree() at _kfree+0x262
       dal_vector_destruct() at dal_vector_destruct+0x20
       (call trampoline in sys_sbrk body) -> syscall2()
```

   (panic-baseline-doublefree.log — serial console capture.)

**Primitive**: confirmed double-free of a 64-byte DRM-slab chunk, attributed
to the vendored function itself. On this KKASSERT-enabled dev kernel the slab
catches it; on release kernels (no INVARIANTS) the chunk would re-enter the
per-CPU magazine twice — the classic aliasing primitive (two kmallocs of the
same size returning the same pointer). Latent (no in-tree second destruct),
same severity class as DF-2068/DF-5051: Low.

**Fix validation** (fix-semantics-validation.log):
- baseline (this run): destruct #2 → panic "already free" (reproduced).
- with the fix's state change emulated (kmem-write NULL over `container`
  between the destructs — precisely what `vector->container = NULL;` in
  fix.diff makes the function do): destruct #2 returns rc=0, **no panic**,
  and two fresh 64-byte allocations return DISTINCT pointers
  (`0xfffff80116c07b00` vs `0xfffff80116c07b40`). Guest stays up.
The kfree(NULL) no-op relies on the DRM kfree macro's existing NULL guard
(sys/dev/drm/include/linux/slab.h:36-39), verified in the disassembly of the
running `dal_vector_destruct` (`test %rdi,%rdi; je` at +0x12).
fix_status: **fixed** (semantics runtime-validated; source diff included).

**Artifacts**: panic-baseline-doublefree.log, fix-semantics-validation.log,
run-baseline.sh, run-fixcheck.sh, kv.c, mkpatch.c, vectorpoc.c, fix.diff.

**Guest**: DragonFly 6.5-DEV #0 Jul 2 2026 (X86_64_GENERIC), QEMU/KVM; left
clean (with-src snapshot).

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