# VERDICT — DF-5149 (pps no-detach / make_dev-without-destroy_dev)

**status: not_reproduced | reproduced: false | impact: none | confidence: certain (structural gap)**
**guest: DragonFly dfbsd 6.5-DEVELOPMENT #0 ... x86_64 (X86_64_GENERIC), up, clean**

## What was claimed

`ppsattach()` discards the cdev returned by `make_dev()` (sys/dev/misc/pps/pps.c:103-104)
and the driver method table has no `device_detach` (pps.c:190-197 — only
identify/probe/attach). Consequences: `/dev/ppsN` and the ppbus child are
kernel-lifetime objects; a module-built pps can never unload; and any
future naive detach would free `struct pps_data` while a client has the
device open — at which point pps owns the ppbus and has `ppsintr`
registered (pps.c:118-133), i.e. a guaranteed UAF-on-interrupt. Family:
DF-5006 (acpi_cpu), DF-5074 (acpi_acad), DF-5091 (acpi_button).

## What was run (see run.log, trigger.sh)

1. `uname -a` — DragonFly 6.5-DEVELOPMENT, X86_64_GENERIC.
2. `kldstat -v | grep -iE "pps|ppbus"` — **empty**: no pps (and no ppbus)
   driver registered anywhere on the baseline system.
3. `ls /boot/kernel | grep -E "^pp"` — only `ppbus.ko` (the *bus* half).
   **There is no pps.ko**: `dev/misc/pps/pps.c` is `optional pps`
   (sys/conf/files:1216) and no module or kernel config on this guest
   requests it (`grep ppbus|lpt|ppi|pps` on X86_64_GENERIC matches only
   `device ulpt`, the USB printer, line 308).
4. `ls -l /dev/pps*` — no such device nodes exist.
5. `kldload ppbus` → loads, but dmesg gains **no attach lines** and no
   `/dev/ppi*`, `/dev/lpt*`, `/dev/pps*` appear: **QEMU exposes no ISA
   parallel port**, so no ppbus bus instance ever probes. `kldunload ppbus`
   → rc=0 (it owned zero devices). Guest residue: none.
6. Guest alive (`uptime`), no panic.

## Why not_reproduced (honest classification)

The structural gap is certain from source. The runtime half cannot even be
set up on this guest: the pps driver code is present in neither the builtin
kernel nor any loadable module, and even with ppbus.ko loaded there is no
parallel-port hardware for a ppbus (hence pps) device to attach to. So no
`/dev/ppsN` exists, nothing to open, nothing to unload, no fault to
trigger. (Note: the audit conventions list "lpt/ppi parallel ... pps" as
QEMU-live; that is stale for this snapshot — recorded here for the
orchestrator.)

Exploit chain: none possible on any current configuration. Unprivileged
users cannot kldload/kldunload (priv required); there is no devd/devctl
detach operation; the missing DETACH method makes module unload abort
before freeing anything — the same structural safety DF-5074/DF-5091
documented.

## Exploitability ceiling (if a detach path is ever added)

`device_detach(pps)` frees `struct pps_data` while an open fd keeps
`pps_open == 1` (ppbus owned, `ppsintr` registered) → next parallel-port
IRQ executes `ppsintr` on freed memory (pps.c:159-174) → UAF with
hardware-timed trigger. The proposed fix returns EBUSY while open, which
closes exactly that window, plus `destroy_dev()` for the leaked cdev.

## Pass-2 depth recorded alongside (killed candidates)

- **ioctl size/dir forging**: `pps_ioctl()` switches on the full cmd word
  (kern_clock.c:1649); `mapped_ioctl()` sizes the kernel buffer from
  IOCPARM_LEN(cmd) (sys_generic.c:668-697). A forged `_IO('1',6)`-style
  cmd falls to `default: ENOTTY` — no OOB via size confusion.
- **intr handler accumulation across open/close**: killed —
  `ppb_release_bus()` force-tears the registration down
  (sys/bus/ppbus/ppbconf.c:514-521), so the ppsclose comment is accurate.
- **SETPARAMS mode bits**: masked by `ppscap` (pps.c:76 + kern_clock.c:1656);
  ECHOCLEAR/CAPTURECLEAR are not in the cap set for this driver.
- **user assert/clear offsets**: only ever added into this instance's
  timestamps (kern_clock.c:1782-1788) — output distortion of one's own
  FETCH, not memory unsafety.
- **FETCH timeout**: `tsleep(PCATCH)` (kern_clock.c:1626) — killable; no
  callout to drain.
- **NULL softc via bogus minor**: unreachable — devfs only exposes nodes
  made by `make_dev` (unit == attached device), no clone handler.
- **DF-2149 aggravation observed while auditing close**: `ppsclose()`
  writes `ppb_wdtr(0)/ppb_wctr(0)` unconditionally and ignores
  `ppb_release_bus()`'s return (pps.c:149-153), so a close by a
  non-owning opener (possible because of the boolean open flag) writes
  bus registers without owning the bus. Same root cause as DF-2149 —
  recorded here, not re-filed.

## Fix

`fix.diff`: store the cdev in the softc; add `ppsdetach()` returning EBUSY
while `pps_open`, else `destroy_dev()`. `git apply --check` clean; not
build-validated (nothing to A/B on this guest — see verdict.json
fix_status: not_testable).

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