# DF-0701 — Heap OOB read in `centre_conf` `bcopy` + integer overflow in size math

> Same ipfw3_sync dead-code caveat as DF-0700 — see that VERDICT for the full
> unreachability proof. Summary: the bug is real at source level; the function is
> unreachable because `ip_fw3_ctl_sync_ptr` (`ip_fw3.c:133`) is never assigned.

## Verdict

**NOT REPRODUCED — LATENT dead code.** Real source bug; unreachable on this kernel
(same dead dispatcher as DF-0700). Fix is defense-in-depth.

## The bug (source-level trace)

`ip_fw3_ctl_sync_centre_conf()` (`sys/net/ipfw3_basic/ip_fw3_sync.c:137-154`) has
**three** compounding defects in 12 lines:

```c
struct ipfw3_ioc_sync_centre *ioc_centre;
int size;                                   /* <-- SIGNED int */
ioc_centre = sopt->sopt_val;
size = ioc_centre->count * LEN_SYNC_EDGE;   /* :143 — (1) int*size_t truncation;
                                             *         (2) no count bound */
if (fw3_sync_ctx.count == 0) {
    fw3_sync_ctx.edges = kmalloc(size, ...); /* :145 — tiny/negative alloc on wrap */
} else {
    fw3_sync_ctx.edges = krealloc(..., size, ...);  /* :147 */
}
                                            /* (3) no NULL check on edges */
fw3_sync_ctx.count = ioc_centre->count;
bcopy(ioc_centre->edges, fw3_sync_ctx.edges,
      ioc_centre->count * LEN_SYNC_EDGE);   /* :151 — uses size_t math, NOT `size` */
```

`LEN_SYNC_EDGE = sizeof(struct ipfw3_sync_edge) = 8` (`ip_fw3_sync.h:46-50`).

**(a) Heap OOB read off `sopt->val`.** `count` is never validated against
`sopt->sopt_valsize`. The `bcopy` at `:151` reads `count * 8` bytes starting at
`ioc_centre->edges`, but the user only supplied `sopt_valsize` bytes. With
`count = 1000` and `sopt_valsize = 8` (just the `count` field, no edges), the
`bcopy` reads ~8 KB past the end of the `sopt->val` kernel heap buffer — a
**kernel heap OOB read** whose contents are then stored into `fw3_sync_ctx.edges`
and are exfiltrable back to userspace via `ip_fw3_ctl_sync_show_conf`
(`:115-116`, which `bcopy`s `fw3_sync_ctx.count * LEN_SYNC_EDGE` bytes out).

**(b) Signed-integer overflow → tiny alloc + huge bcopy = heap overflow.** `size`
is `int`. `ioc_centre->count * LEN_SYNC_EDGE` promotes `count` (int) with
`LEN_SYNC_EDGE` (size_t) to size_t, but the assignment truncates back to `int`.
For `count = 0x40000000` (≈10⁹), `count * 8 = 0x200000000` (8 GiB) truncates to
`0` in 32-bit `int` → `kmalloc(0, ...)` returns a minimal slab chunk → the
`bcopy` then writes 8 GiB through it → catastrophic kernel heap overwrite +
immediate panic. (The same `count` also drives the BSS OOB of DF-0700 once
`centre_start` runs.)

**(c) NULL deref panic.** With `count` such that the wrapped/negative `size` makes
`kmalloc` fail (`M_NOWAIT`, no NULL check), `fw3_sync_ctx.edges = NULL` and the
`bcopy` writes to NULL → fatal trap.

## Why unreachable on this kernel

Identical to DF-0700: `centre_conf` is a sockopt handler reachable only through
`ip_fw3_ctl_sync_sockopt`, which is only dispatched via `ip_fw3_ctl_sync_ptr`
(`ip_fw3.c:133`) — and that pointer is **never assigned anywhere in the tree**
(grep-verified). Plus the base kernel's ipfw3 stub returns `ENOPROTOOPT` until
`kldload ipfw3`. Empirically confirmed by `ipfw3_sync_deadpath` (see `run.log`):
issuing the sockopt returns `ENOPROTOOPT`.

## Fix (defense-in-depth)

Validate `count` against `MAX_EDGES` **and** `sopt_valsize`, switch `size` to
`size_t` (no truncation), and NULL-check the alloc. See `fix.diff`:

```c
struct ipfw3_ioc_sync_centre *ioc_centre;
size_t size, need;                                   /* size_t, not int */
ioc_centre = sopt->sopt_val;
if (ioc_centre->count < 0 || ioc_centre->count > MAX_EDGES)   /* DF-0700 bound */
    return EINVAL;
need = sizeof(*ioc_centre) + (size_t)ioc_centre->count * LEN_SYNC_EDGE;
if (sopt->sopt_valsize < need)                       /* DF-0701: bound vs user buf */
    return EINVAL;
size = (size_t)ioc_centre->count * LEN_SYNC_EDGE;    /* size_t: no wrap */
...
if (fw3_sync_ctx.edges == NULL) { fw3_sync_ctx.count = 0; return ENOMEM; }
```

This closes (a) the OOB read (valsize check), (b) the int overflow (size_t), and
(c) the NULL deref (NULL check). (The `MAX_EDGES` check is the same line as
DF-0700's fix; the two findings overlap on `centre_conf`.)

## Fix validation

- `fix.diff` applies cleanly to the original source.
- Compile-validated: applied (as the `centre_conf` rewrite, superseding DF-0700's
  check) with DF-0702/0703, `make -j6 nativekernel` → **rc=0**, `ip_fw3_sync.c`
  compiles with `-Werror`.
- `fix_status: not_testable` — dead code; validated applies + compiles.

## Files

| file | desc |
|---|---|
| `ipfw3_sync_deadpath.c` | empirical dead-path demo |
| `fix.diff`              | count vs MAX_EDGES + sopt_valsize; size_t size; NULL check |
| `run.log`               | dead-path demo output (ENOPROTOOPT) |
| `env.txt`               | guest environment |

## Kernel references (confirmed)

- `sys/net/ipfw3_basic/ip_fw3_sync.c:137-154` — `centre_conf`
- `sys/net/ipfw3_basic/ip_fw3_sync.c:143` — `int size = count * LEN_SYNC_EDGE` (signed truncation)
- `sys/net/ipfw3_basic/ip_fw3_sync.c:145` — `kmalloc(size, M_NOWAIT)` (wrapped/negative size, no NULL check)
- `sys/net/ipfw3_basic/ip_fw3_sync.c:151-152` — `bcopy(ioc_centre->edges, ..., count*LEN_SYNC_EDGE)` (OOB read)
- `sys/net/ipfw3_basic/ip_fw3_sync.c:115-116` — `show_conf` exfiltrates `edges` back to user
- `sys/net/ipfw3_basic/ip_fw3_sync.h:46-50` — `LEN_SYNC_EDGE = sizeof(struct ipfw3_sync_edge) = 8`
- `sys/net/ipfw3/ip_fw3.c:133` — `ip_fw3_ctl_sync_ptr = NULL` (dead dispatcher)
