# DF-0585 — PoC evidence pack

Reproduces the `TAPSIFINFO` ifnet-serializer orphan in `sys/net/tap/if_tap.c`
(`tapioctl`). A confirmed **local DoS** that permanently wedges a tap(4)
interface, requiring a reboot to recover.

## Root cause (confirmed in source)

`tapioctl()` at `sys/net/tap/if_tap.c:726` acquires the interface serializer
with `ifnet_serialize_all(ifp)` at **line 738** and releases it **only** at
**line 832** (`ifnet_deserialize_all(ifp)`), after the `switch`. The
`TAPSIFINFO` case (line 742) early-returns `EPROTOTYPE` at **line 745** when
`tapp->type != ifp->if_type`, **without releasing the serializer**. The lock is
orphaned for the lifetime of the interface. Every later
`ifnet_serialize_all(ifp)` then blocks forever — including `tapclose()` at
**line 426**, so even closing the file descriptor hangs.

## Reachability / privilege (verified)

- `/dev/tap` clone node is created `0600` `UID_ROOT`/`GID_WHEEL`
  (`if_tap.c:183-185`, `if_tap.c:377-378`), so opening it requires root or
  wheel membership at the devfs layer.
- `tapopen()` (`if_tap.c:323-327`) additionally requires
  `caps_priv_check(SYSCAP_RESTRICTEDROOT)` unless `net.link.tap.user_open=1`.
  Even with that sysctl set, the devfs node remains `0600 root:wheel`, so an
  unprivileged user still gets `EACCES` (confirmed: `maxx` uid 1001 not in
  wheel → `Permission denied`).
- **Net: this is a root/wheel-reachable local DoS.** The CVSS `PR:L` in the
  finding is generous; effective privilege is **High** (or wheel membership).
  The defect is still real and worth fixing: a privileged network
  configuration tool (commonly root, e.g. a VPN/bridge/jail setup helper) can
  trivially wedge the interface and force a reboot.

## Files

- `leak_tap_lock.c` — minimal trigger (vendored correct 8-byte `struct tapinfo`;
  fork-dance so the harness doesn't hang).
- `build.sh` — `cc -Wall -O2 -o leak_tap_lock leak_tap_lock.c`.
- `run.sh` — loads `if_tap`, runs the trigger, then a fully-detached
  `ifconfig` corroboration.
- `build.log` / `run.log` / `corroborate.log` — full logs.
- `env.txt` — guest environment.
- `VERDICT.md` — full narrative.
- `fix.diff` — git-apply-able one-line fix.
- `manifest.json` — artifact catalog.

## Build & run (as root on the DragonFly guest)

```
./build.sh
./run.sh                # loads if_tap if needed, triggers, corroborates
```

**Note on tap being a module:** in `X86_64_GENERIC`, tap(4) is a KLD module
(`if_tap.ko`), not a static kernel device. The bug and the fix live in
`if_tap.ko`. When validating `fix.diff`, install BOTH the rebuilt kernel AND
the rebuilt `/usr/obj/.../net/tap/if_tap.ko` to `/boot/kernel/if_tap.ko`
(otherwise `kldload if_tap` loads the old unpatched module and the wedge
reproduces even on a rebuilt kernel).

## Expected result (bug present)

```
[*] TAPSIFINFO returned -1: errno=41 (Protocol wrong type for socket)  [EPROTOTYPE=41]
[*] tapioctl() early-returned at if_tap.c:745 WITHOUT releasing
[*] the ifnet serializer acquired at if_tap.c:738 -> LOCK ORPHANED
[+] ===================================== PROOF =====
[+] child pid <N> still running 3s into its close() call
[+] -> tapclose() is wedged at ifnet_serialize_all() (if_tap.c:426)
[+] -> DF-0585 REPRODUCED: interface permanently wedged
```

On a **FIXED** kernel (with `fix.diff` applied to `if_tap.ko`) the same
binary instead reports the honest negation — the child *exits* and the
serializer is released:

```
[+] child pid <N> EXITED after close() (status=0x0)
[+] -> tapclose() completed; serializer released
[+] -> DF-0585 NOT reproduced: no wedge (FIXED kernel)
```

The detection uses `waitpid(WNOHANG)` (not `kill(pid,0)`, which cannot
distinguish a wedged child in D-sleep from an exited zombie).

and the detached corroboration:

```
BG_PID=<N>
CORROB_IFCONFIG_WEDGED      # ifconfig tap0 still in D-sleep after 6s
```

After this the tap0 interface (and the stuck kernel thread) are unusable.
**Recovery requires a reboot** (`vm.sh reset`). This is a destructive hang,
not a panic — the guest does not drop to DDB.

## How the proof works

The trigger opens `/dev/tap` (clone → tap0), issues `TAPSIFINFO` with
`type=0xFF` (≠ `IFT_ETHER`=6) to hit the buggy early-return at line 745, then
**forks**. The parent drops its file reference first; the child's `close()` is
therefore the final reference, so it runs `tapclose()` →
`ifnet_serialize_all(ifp)` at line 426, which blocks forever on the orphaned
serializer. The parent observes the child is still alive 3 s later and reports
PROOF, then exits (leaving the child wedged in the kernel). A fully-detached
`ifconfig tap0` corroborates that the *whole interface* (not just this fd) is
wedged.
