PF_LOCAL sockaddr unparse: sun_len<pathoff underflow -> giant bcopy -> stack OOB write (DF-0509 v1 twin)
Summary
ng_ksocket_sockaddr_unparse(:321-339) PF_LOCAL: pathlen=sun->sun_len-pathoff(pathoff=2). NO validation sun_len>=pathoff. If sun_len<2: pathlen negative int -> bcopy(sun->sun_path, pathbuf, pathlen)(:331) interprets as size_t~2^64 -> massive stack OOB write into 256-byte pathbuf + OOB read from sun_path. pathbuf[pathlen]=\0(:332) writes at negative index. Identical to DF-0509(ng7). Generic helper ng_parse_generic_sockdata_getLength(:169-177) DOES guard this (returns 0 when sa_len<SADATA_OFFSET) but unparse omits guard. Medium (not High like ng7) because v1 ksocket is less commonly deployed and kernel-produced sockaddrs always have valid sun_len. Fix: pathlen=(sun_len<pathoff)?0:sun_len-pathoff.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0521 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df0521.c | trigger-source | static arithmetic demonstration of sun_len<pathoff underflow | 3.5 KB | view raw |
| build.sh | build-script | cc -O2 -o df0521 df0521.c | 98 B | view raw |
| run.sh | run-script | ./df0521 | 243 B | view raw |
| VERDICT.md | verdict | mechanism, reachability analysis, fix rationale | 3.6 KB | β raw |
| fix.diff | suggested-fix | clamp pathlen to 0 when sun_len<pathoff (mirrors generic helper) | 513 B | view raw |
| run.log | run-log | static PoC output showing (size_t)-1 = ~2^64 | 625 B | view raw |
| env.txt | environment | uname + cc version | 188 B | view raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-0521 β PF_LOCAL sockaddr unparse sun_len<pathoff underflow
Verdict: CODE-PATH CONFIRMED (defense-in-depth); latent β no live unprivileged trigger
Mechanism (confirmed at code level)
ng_ksocket_sockaddr_unparse() in
sys/netgraph/ksocket/ng_ksocket.c:313-339 converts a struct
sockaddr_un to ASCII for netgraph status output. The PF_LOCAL branch
at line 321 computes:
const int pathoff = OFFSETOF(struct sockaddr_un, sun_path); /* = 2 */
const struct sockaddr_un *sun = (const struct sockaddr_un *)sa;
const int pathlen = sun->sun_len - pathoff; /* SIGNED int! */
char pathbuf[SOCK_MAXADDRLEN + 1]; /* 256-byte stack buf */
bcopy(sun->sun_path, pathbuf, pathlen); /* size_t -> ~2^64 */
pathbuf[pathlen] = '\0'; /* neg-index write */
If sun->sun_len < 2 (i.e. < pathoff), pathlen becomes a negative
int. When bcopy interprets it as size_t, it becomes ~2^64,
producing a massive OOB write into the 256-byte stack buffer (and a
corresponding OOB read from sun->sun_path). pathbuf[pathlen] = 0
then writes at a negative stack offset.
The static df0521 PoC demonstrates the arithmetic:
well-formed: sun_len=14, pathoff=2 -> pathlen=12 (OK)
malformed: sun_len=1, pathoff=2 -> pathlen=-1
(size_t)pathlen = 18446744073709551615 bytes
target buffer: pathbuf[255+1] (stack)
pathbuf[pathlen] = '\0' writes at stack offset -1
=> stack OOB write of ~18446744073709551615 bytes + neg-index write
Reachability β why this is defense-in-depth
unparse is invoked by the netgraph subsystem when serializing a
sockaddr to ASCII (e.g. for ngctl msg ... getname /
getpeername text output). The sockaddr it operates on comes from
so_pru_peeraddr / so_pru_sockaddr, which the kernel always fills
with a valid sun_len. No path in the kernel currently produces a
sockaddr_un with sun_len < 2.
Additionally, the netgraph control socket itself is root-only:
$ ngctl list ngctl: socket: Operation not permitted
An unprivileged user cannot even reach the unparse code path β driving
ng_ksocket requires socket(AF_NETGRAPH) which needs root.
The bug would become live only if (a) some other kernel path produced
a malformed sockaddr_un, or (b) netlink-style user-supplied data
reached unparse directly. The identical twin in netgraph7
(DF-0509) has the same property.
The sibling helper ng_parse_generic_sockdata_getLength
(sys/netgraph/ksocket/ng_ksocket.c:169-177) does guard this case:
return (sa->sa_len < SADATA_OFFSET) ? 0 : sa->sa_len - SADATA_OFFSET;
β the PF_LOCAL special-case unparse simply omits the same guard.
Severity assessment
Medium is appropriate: real code defect with a stack-OOB-write primitive, but gated behind (1) root access to drive netgraph, AND (2) a separate kernel bug to produce the malformed input. Matches the finding's own framing ("Medium (not High like ng7) because v1 ksocket is less commonly deployed and kernel-produced sockaddrs always have valid sun_len").
Fix
fix.diff adds the missing guard, mirroring the existing
ng_parse_generic_sockdata_getLength pattern:
const int pathlen = (sun->sun_len < pathoff) ? 0 :
sun->sun_len - pathoff;
A one-line clamp that eliminates the underflow unconditionally.
Build / run
ssh dfbsd-maxx 'mkdir -p poc/DF-0521'
scp findings/poc/DF-0521/{df0521.c,build.sh,run.sh} dfbsd-maxx:poc/DF-0521/
ssh dfbsd-maxx 'cd poc/DF-0521 && sh ./build.sh && sh ./run.sh'
# prints the underflow arithmetic; no kernel side-effect (defense-in-depth)
Fix verification
not_testablecompile validated
see evidence pack
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source-confirmed. ng_ksocket PF_LOCAL sun_len
No comments yet.