# DF-0449 — Heap buffer overflow in `ng_string_parse` (CWE-787)

**Severity:** High (root→kernel heap corruption; root-gated trigger)
**Class:** CWE-787 Out-of-bounds Write + secondary CWE-125 OOB Read
**File/lines:** `sys/netgraph/netgraph/ng_parse.c:704-720` (`ng_string_parse`)
**Caller:** `sys/netgraph/netgraph/ng_base.c:1578-1648` (`NGM_ASCII2BINARY`)

## The bug

`ng_string_parse()` parses a quoted netgraph string and copies the decoded
result into the caller-supplied buffer `buf` of size `*buflen`. It computes
`len = strlen(sval) + 1` and unconditionally executes `bcopy(sval, buf, len)`
at **line 716** with **no check that `len <= *buflen`**. Every sibling parse
function in the same file performs this bounds check before its bcopy:

| Function | Check | Line |
|---|---|---|
| `ng_int8_getDefault` | `if (*buflen < sizeof(int8_t)) return (ERANGE)` | 385 |
| `ng_int16_getDefault` | `if (*buflen < sizeof(int16_t)) return (ERANGE)` | 479 |
| `ng_int32_getDefault` | `if (*buflen < sizeof(int32_t)) return (ERANGE)` | 573 |
| `ng_int64_getDefault` | `if (*buflen < sizeof(int64_t)) return (ERANGE)` | 666 |
| `ng_string_getDefault` | `if (*buflen < 1) return (ERANGE)` | 742 |
| `ng_fixedstring_parse` | `if (strlen(sval)+1 > fi->bufSize) return (E2BIG)` | 774 |
| **`ng_string_parse`** | **NONE** | **716** |

## Trigger path (root-only)

1. `socket(AF_NETGRAPH, SOCK_DGRAM, NG_CONTROL)` — requires
   `SYSCAP_RESTRICTEDROOT` (`ng_socket.c:172`), i.e. root.
2. `sendto(csock, NGM_ASCII2BINARY_msg, ..., ".")` — sends to own socket node.
3. `NGM_ASCII2BINARY` (`ng_base.c:1578`) allocates `bufSize=2000`, builds a
   response with `binary->data` = 2000-byte buffer.
4. With `NGF_RESP` set in the embedded message and `cmdstr="textstatus"`,
   `argstype = c->respType = &ng_parse_string_type` (`ng_base.c:1631-1632`).
5. `ng_parse()` → `ng_string_parse()` → `bcopy(sval, buf, len)` where
   `len = strlen(sval)+1`. A string of 2500 chars ⇒ `len=2501` ⇒ **501-byte
   heap overflow** past the 2000-byte `binary->data` into the adjacent slab
   object.

**Secondary OOB read:** `ng_base.c:1644` sets `arglen` to the inflated `len`;
`ship_msg` (`ng_socket.c:737`) does `m_devget(msg, sizeof(ng_mesg)+arglen)` =
reads 2605 bytes from a 2104-byte allocation ⇒ 501-byte OOB read of adjacent
heap. Note: the OOB-read window always equals the overflow window (`len-2000`),
so it reads back the attacker's *own* overflowed bytes — not pre-existing
kernel data. The **primary** impact is the heap corruption (CWE-787).

## How to reproduce

```sh
./build.sh                        # cc -o ng_overflow ng_overflow.c
# as root:
kldload netgraph && kldload ng_socket
./run.sh                          # ./ng_overflow (run as root)
```

### Expected output (bug present — unpatched `#0` kernel)
```
[*] sending NGM_ASCII2BINARY: string=2500 A's, bufSize(buflen)=2000, expected overflow=501 bytes
[+] sendto returned 2607 bytes
[+] recvfrom returned 2605 bytes (response)
[*] response header.arglen = 2553 (alloc was 2000)
[!!] OVERFLOW CONFIRMED: arglen=2553 > 2000; secondary OOB read leaked ~553 bytes of adjacent heap
```

### Expected output (FIXED kernel/module)
```
[*] sending NGM_ASCII2BINARY: string=2500 A's, bufSize(buflen)=2000, expected overflow=501 bytes
[!] sendto failed: Result too large          ← ERANGE from the new bounds check
```

## Impact

**Root → kernel heap corruption.** The trigger is root-gated
(`SYSCAP_RESTRICTEDROOT` on the ng_socket control domain), so this is not an
unprivileged-to-root escalation. It is a **root→kernel integrity violation**:
a root process (including a compromised setuid-root program or a jail context
where root is available but kernel isolation should hold) can corrupt arbitrary
adjacent slab objects with attacker-controlled bytes. A successful slab-grooming
escalation to `uid=0`-equivalent kernel compromise is conceivable, but is
blocked on this audit guest by **INVARIANTS** (KKASSERT panics on slab
free-list corruption, proven by DF-0783/DF-0028). On a production kernel
without INVARIANTS, the 501-byte attacker-controlled heap write is a viable
corruption primitive.

## Files

- `ng_overflow.c` — trigger PoC (sends oversized NGM_ASCII2BINARY)
- `build.sh` / `run.sh` — reproducible build/run
- `run.log` — baseline (unpatched) run, full output
- `fix_run.log` — patched-module run, full output
- `leak_sample.txt` — hex of the overflowed/OOB-read bytes
- `env.txt` — guest environment
- `fix.diff` — the fix (add `len > *buflen` check, mirroring siblings)
- `fix_build.log` — module build + disassembly verification
- `VERDICT.md` — full analysis
- `manifest.json` — artifact catalog
