# DF-5698 — kernel inet_ntop never sets errno (documented contract unimplemented)

**Severity: Info** — correctness/contract divergence, no in-tree security impact.
**File:** `sys/libkern/inet_ntop.c` (pass-2 GLM 5.3 re-audit)

## What was verified (the whole file, not just this finding)

A userland differential rig compiles the **audited kernel TU itself**
(sha256-verified identical host↔guest: `6ddc914991203a5f4e54544a8d8c511790e0428e3400cf376d2ad49d87`)
as a renamed symbol (`-Dinet_ntop=dk_inet_ntop -Dksnprintf=snprintf -Dksprintf=sprintf`)
and compares it against DragonFly libc's `inet_ntop` (the errno-setting twin at
`lib/libc/inet/inet_ntop.c`) over ~172.5M total comparisons:

- IPv4 string equivalence (corner cross-product 194k, sequential 4M, random 40M)
- IPv6 string equivalence (structured zero-run space 567 combos, RFC4291
  specials, random 40M, zero-biased bytes)
- **Size-gate decision equivalence at every size**: v4 sizes 0..16, v6 sizes
  0..46 — kernel accept/reject boundary identical to libc at every step;
  accepted strings identical, `strlen+1 <= size` always
- **Guard-page overflow canary**: dst placed so `dst[size]` is the first byte
  of a `PROT_NONE` page (and for `size==0`, dst *is* the PROT_NONE page start —
  zero writable bytes). 900k knife-edge calls, 400k accepted writes, **zero
  faults** — proof there is no off-by-one anywhere in the size gate
- af-dispatch decision equality for 16 af values (DragonFly: AF_INET=2,
  AF_INET6=28)
- `inet_pton` round-trip on all IPv4 outputs

**Result: `MISMATCH=0` on every run.** The formatter is memory-safe and
byte-equivalent to its libc twin. The *only* divergence: the kernel copy never
sets `errno` (P6 in run.log: `errno-after=0` for both too-small-dst and
bad-af), although its own doc-comment promises "NULL (see errno)"
(inet_ntop.c:38-39) and the libc twin sets `EAFNOSUPPORT` (lib/libc/inet/inet_ntop.c:62)
and `ENOSPC` (:88,:169,:187). No in-tree kernel consumer reads errno after
inet_ntop (all 12 call sites are kprintf arguments in sys/netinet/ip_icmp.c
with correctly sized INET_ADDRSTRLEN buffers, and kprintf renders NULL %s as
"(null)" — sys/kern/subr_prf.c:750-753), hence Info, not higher.

## How to reproduce

On the host (from the repo root), with the QEMU guest up:

    ./dfbsd-qemu/vm.sh up          # if down
    sh findings/poc/DF-5698/build.sh   # transports rig to guest, builds (guest gcc 8.3)
    sh findings/poc/DF-5698/run.sh     # full differential + errno fix validation

Expected decisive output (run.3.log):

    P6: size-too-small kernel-returns=0x0 errno-after=0 (ENOSPC=28 EAFNOSUPPORT=47)
    P7: guardpage calls=500000 accepted=200000 (no SIGSEGV)
    TOTAL=25889126 MISMATCH=0 ROUNDTRIP_BAD=0
    variant=BASELINE too_small: ret=0x0 errno=0(NOT-SET) | bad_af: ret=0x0 errno=0(NOT-SET)
    variant=FIXED    too_small: ret=0x0 errno=28(ENOSPC) | bad_af: ret=0x0 errno=47(EAFNOSUPPORT)

The `errno=0(NOT-SET)` lines on BASELINE are the finding; the FIXED lines show
`fix.diff` implements the documented contract without changing any formatting
behavior (fixed TU also passes the full differential: `MISMATCH=0`).

## Files

    harness.c            the differential driver (P1..P7 phases)
    fixcheck.c           errno-contract probe (baseline vs FIXED variant)
    inet_ntop_fixed.c    audited TU + errno fix (built on host from sys/ copy)
    shim/sys/systm.h     userland shim for the kernel TU
    shim/netinet/in.h    empty shim (avoids libc decl clash after -D rename)
    build.sh run.sh      host-side orchestration (guest via ssh aliases)
    build.log run.log run.2.log run.3.log env.txt fix_run.log fix_build.log
    fix.diff             git-apply-able fix (validated: git apply --check clean)
    manifest.json        machine catalog
    verdict.json         machine verdict (persist_poc.py / persist_fix.py input)
