# DF-0761 — VERDICT

**Status:** REPRODUCED (policy/logic bypass) + FIX VALIDATED
**Impact:** `policy-bypass` — securelevel>=3 firewall-immutability gate defeated (privileged-only)
**Confidence:** certain
**Severity:** Medium (matches finding)

---

## Verdict (one line)

The ipfw3 `IP_FW_X` setsockopt dispatch path (`sys/net/ipfw3/ip_fw3_glue.c:50-62`) omits the
`securelevel >= 3` firewall-mutability gate that ALL sibling glue layers enforce, so a root
process can keep flushing/altering the ipfw3 ruleset after the system has gone immutable —
**confirmed on the unpatched `#0` kernel** (`setsockopt(IP_FW_X, FLUSH) → rc=0` at
`kern.securelevel=3`) and **closed on the single-fix `#1` kernel** (`→ EPERM`).

---

## Mechanism (trigger → effect, every hop cited)

1. **Trigger (userspace, root).** A root process opens a raw socket
   (`socket(AF_INET, SOCK_RAW, IPPROTO_RAW)`) and issues
   `setsockopt(IPPROTO_IP, IP_FW_X, &xhdr, sizeof(xhdr))` where `xhdr.opcode` selects the
   operation (`IP_FW_FLUSH=52`, `IP_FW_ADD=50`, `IP_FW_DEL=51`, etc., per
   `sys/net/ipfw3/ip_fw3.h:372-377`).  `IP_FW_X` itself is `49` (`sys/netinet/in.h:389`).

2. **Dispatch.** `rip_ctloutput` (`sys/netinet/raw_ip.c:385-387`) routes `IP_FW_X` SOPT_SET
   calls to `ip_fw3_sockopt()` (`sys/netipfw3/ip_fw3_glue.c:50-62`).

3. **The bug — missing gate.** `ip_fw3_sockopt()` checks only `IPFW3_LOADED` and unconditionally
   calls `ip_fw_ctl_x_ptr(sopt)` (line 58). There is **no** `securelevel >= 3` check anywhere
   in the function. Contrast with every sibling:
   - `sys/net/ipfw/ip_fw2_glue.c:65-69` — `ip_fw_sockopt` HAS the gate:
     `if (sopt->sopt_name == IP_FW_ADD || (sopt->sopt_dir == SOPT_SET && sopt->sopt_name != IP_FW_RESETLOG)) { if (securelevel >= 3) return EPERM; }`
   - `sys/net/dummynet3/ip_dummynet3_glue.c:159-163` — `ip_dn_sockopt` HAS the gate:
     `if (sopt->sopt_dir == SOPT_SET) { if (securelevel >= 3) return EPERM; }`
   - `sys/net/pf/pf_ioctl.c:992` — pf ioctl path HAS `if (securelevel > 1)` gate.

4. **Effect.** At `kern.securelevel >= 3` (the "really-really secure" / network-immutable
   level, settable via `sysctl kern.securelevel=3` and **irreversible** in a running kernel
   per `sys/kern/kern_mib.c:257-258`), a root process can still FLUSH / ADD / DEL / SET-TOGGLE
   / NAT / dummynet / table / sync-reconfigure the ipfw3 ruleset — defeating the exact
   guarantee securelevel 3 exists to provide (containment of a compromised root).

**Threat model.** This is a **privileged-only** policy/logic bypass — NOT memory corruption.
The setsockopt needs `SYSCAP_NONET_RAW` (root). There is **no unprivileged escalation
chain**; the impact is a defeated securelevel boundary for a post-compromise root attacker,
which is precisely the scenario securelevel 3 is designed to contain (a root attacker who
must NOT be able to alter the firewall ruleset, flush it to re-open attack surface, or
re-NAT to exfiltrate). Hence **Medium**, not High.

---

## Exploit chain

`none` (non-corruption class). This is a privileged policy-bypass. The "primitive" is the
ungated setsockopt itself; the impact ceiling is "root at securelevel>=3 can mutate the
ipfw3 firewall arbitrarily", characterized above. No heap grooming, victim object, or
uid=0 conversion applies.

---

## Reproduction evidence (decisive lines)

**Baseline — unpatched `#0` kernel (`6.5-DEVELOPMENT #0`, Thu Jul 2 06:02:54 UTC 2026):**
```
=== [3] raise kern.securelevel to 3 (network-immutable gate) ===
kern.securelevel: -1 -> 3
kern.securelevel: 3
=== [4] attempt IP_FW_X FLUSH at securelevel=3 (the bypass probe) ===
[env] kern.securelevel = 3
[bypass-FLUSH] setsockopt(IP_FW_X, opcode=52) -> rc=0 errno=0 (OK)

=== RESULT: BYPASS CONFIRMED ===
setsockopt(IP_FW_X, FLUSH) succeeded at securelevel=3 -> ipfw3 securelevel>=3 gate is MISSING (DF-0761 reproduced)
```

**Patched — single-fix `#1` kernel (`6.5-DEVELOPMENT #1`, Thu Jul 9 12:10:06 UTC 2026):**
```
=== [3] raise kern.securelevel to 3 (network-immutable gate) ===
kern.securelevel: -1 -> 3
kern.securelevel: 3
=== [4] attempt IP_FW_X FLUSH at securelevel=3 (the bypass probe) ===
[env] kern.securelevel = 3
[bypass-FLUSH] setsockopt(IP_FW_X, opcode=52) -> rc=-1 errno=1 (Operation not permitted)

=== RESULT: GATED (FIXED) ===
setsockopt(IP_FW_X, FLUSH) returned EPERM at securelevel=3 -> securelevel>=3 gate is present (bug closed)
```

(Second patched-kernel run, `fix_run.2.log`, is byte-identical at the decisive line — the
fix is deterministic.)

Full untrimmed logs: `run.log` (baseline), `fix_run.log` + `fix_run.2.log` (patched),
`fix_build.log` (single-fix kernel build, `NK_DONE rc=0`).

---

## PoC changes

Authored from scratch — no prior PoC existed in `findings/poc/DF-0761/` (the finding was
filed but the initial scaffolding had not been seeded). Files written:
- `df0761.c` — minimal C trigger: opens a raw socket, issues `setsockopt(IP_FW_X, {opcode=FLUSH})`,
  reports `rc`/`errno`, and prints a `BYPASS CONFIRMED` / `GATED (FIXED)` / `INCONCLUSIVE` marker.
  Constants (`IP_FW_X=49`, opcodes from `ip_fw3.h:372-377`) are defined in-file so it builds on
  a stock master-DEV install with no kernel headers in `/usr/include`.
- `build.sh` — `cc -O0 -g -o df0761 df0761.c`.
- `run.sh` — orchestrates the test: flips `net.filters_default_to_accept=1` (so loading ipfw3
  does not black-hole ssh — ipfw3 defaults to DENY on load, `ip_fw3.c:1468`), `kldload ipfw3`,
  raises `kern.securelevel` to 3, then runs `./df0761`.

Two PoC bugs fixed during iteration:
1. First run hung ssh — ipfw3's default-deny on `kldload` cut the control channel before the
   test could run. Fixed by setting `net.filters_default_to_accept=1` before `kldload` (this
   does not affect the securelevel-bypass test, which exercises the dispatch path, not the
   default policy).
2. PoC's internal `sysctl` read of `kern.securelevel` used the wrong `KERN_SECURELVL`
   constant (6 → 9, per `sys/sys/sysctl.h:505`); fixed so the PoC's printed securelevel
   matches the kernel's own `sysctl kern.securelevel`.

---

## Recommended fix

`fix.diff` adds the missing gate to `ip_fw3_sockopt()`, matching the **dummynet3 sibling**
form (`sys/net/dummynet3/ip_dummynet3_glue.c:159-163`):

```c
int
ip_fw3_sockopt(struct sockopt *sopt)
{
	int error;

	ASSERT_NETISR0;

	/* Disallow firewall mutations in really-really secure mode, matching
	 * the gate present in every sibling glue layer ... SOPT_GET remains
	 * allowed, consistent with ip_fw2_glue.c and ip_dummynet3_glue.c. */
	if (sopt->sopt_dir == SOPT_SET && securelevel >= 3)
		return EPERM;

	if (IPFW3_LOADED)
		error = ip_fw_ctl_x_ptr(sopt);
	else
		error = ENOPROTOOPT;
	return (error);
}
```

This **matches the finding's proposed fix** (`if(sopt_dir==SOPT_SET&&sopt_name==IP_FW_X)
if(securelevel>=3) return EPERM`) — for ipfw3, `sopt_name` is always `IP_FW_X` at this
point (the sub-opcode is inside the payload), so checking `sopt_dir==SOPT_SET` alone is
equivalent and simpler. It is slightly stricter than the ipfw2 sibling (which carves out
`IP_FW_RESETLOG`), but ipfw3 has no equivalent carve-out requirement and the dummynet3
sibling uses exactly this all-SET-blocked form. A maintainer wanting the RESETLOG
carve-out can parse the `ip_fw_x_header.opcode` later; the minimal correct fix is this one.

---

## Fix validation (Phase 8)

- **Baseline `#0`**: PoC → `BYPASS CONFIRMED` (setsockopt rc=0 at securelevel=3). ✓ bug present.
- **Applied `fix.diff`** to in-guest `/usr/src` via `patch -p1` (hunk succeeded at line 54).
- **Built single-fix kernel** `make -j6 nativekernel KERNCONF=X86_64_GENERIC` → `NK_DONE rc=0`
  (warm obj, ~13 min full rebuild because mkdep regenerated; `.c`-only change).
- **Installed** `kernel.stripped` → `/boot/kernel/kernel`, `kernel.debug` → `/boot/kernel/kernel.debug`,
  clean reboot → `kern.version` now `#1: Thu Jul 9 12:10:06 UTC 2026` (was `#0: Thu Jul 2 ...`).
- **Patched `#1`**: PoC → `GATED (FIXED)` (setsockopt rc=-1 errno=EPERM at securelevel=3).
  Deterministic across 2 runs.
- **`fix_status`: fixed** — bad behavior gone on patched, present on baseline. Clean before/after.
- Reset to `with-src` baseline at end.
