DF-0521 / df0521.c
/* * DF-0521 โ ng_ksocket_sockaddr_unparse() PF_LOCAL sun_len<pathoff underflow * * Bug (sys/netgraph/ksocket/ng_ksocket.c:321-339): * const int pathoff = OFFSETOF(struct sockaddr_un, sun_path); // = 2 * const int pathlen = sun->sun_len - pathoff; // signed underflow * char pathbuf[SOCK_MAXADDRLEN + 1]; * bcopy(sun->sun_path, pathbuf, pathlen); // size_t ~2^64 * pathbuf[pathlen] = '\0'; // neg-index stack write * * If `sun->sun_len < 2`, `pathlen` becomes negative; bcopy interprets it * as `size_t` and copies ~2^64 bytes into the 256-byte stack buffer. * `pathbuf[pathlen]` writes at a negative stack offset. Stack OOB * write + stack info leak โ kernel panic or worse. * * TRIGGER REALITY (per the finding summary): the only path that calls * `unparse` is the netgraph subsystem's serialization of a sockaddr to * ASCII (e.g. ngctl msg GETNAME/GETPEERNAME). The sockaddr comes from * `so_pru_peeraddr` / `so_pru_sockaddr`, which the kernel always fills * with a valid `sun_len`. An unprivileged user cannot reach unparse: * `ngctl` itself requires `socket(AF_NETGRAPH)` which is root-only. * * Therefore this is a **defense-in-depth** bug: it would become * exploitable only if some other kernel path (a) produced a sockaddr_un * with sun_len < 2, or (b) let user-supplied data reach unparse * directly. The identical twin in netgraph7 (DF-0509) has the same * property. Generic helper `ng_parse_generic_sockdata_getLength` * (line 169-177) DOES guard this case โ the PF_LOCAL special-case * unparse omits the guard. * * This PoC is a static demonstration of the math; the actual kernel * trigger requires root + a kernel-side malformed-sockaddr source. * * Build: cc -O2 -o df0521 df0521.c * Run: ./df0521 */ #include <stdio.h> #include <string.h> #include <stddef.h> /* Mirror OFFSETOF(struct sockaddr_un, sun_path) */ struct sockaddr_un_min { unsigned char sun_len; unsigned char sun_family; char sun_path[1]; }; #define PATHOFF (int)offsetof(struct sockaddr_un_min, sun_path) #define SOCK_MAXADDRLEN 255 int main(void) { /* Case 1: well-formed sockaddr_un (pathoff=2, sun_len=14, pathlen=12) */ unsigned char well_formed[] = {14, /*PF_LOCAL*/1, '/','t','m','p','/','f','o','o','.','s','o','c','k'}; int plen_ok = well_formed[0] - PATHOFF; printf("well-formed: sun_len=%u, pathoff=%d -> pathlen=%d (OK)\n", well_formed[0], PATHOFF, plen_ok); /* Case 2: malformed sun_len=1 (< pathoff=2). SIGNED subtraction * yields -1; (size_t)-1 = 18446744073709551615. bcopy would copy * ~2^64 bytes. */ unsigned char malformed[] = {1, /*PF_LOCAL*/1, 'X'}; int pathlen = (int)malformed[0] - PATHOFF; unsigned long as_size_t = (unsigned long)pathlen; printf("malformed: sun_len=%u, pathoff=%d -> pathlen=%d\n", malformed[0], PATHOFF, pathlen); printf(" (size_t)pathlen = %lu bytes\n", as_size_t); printf(" target buffer: pathbuf[%d+1] (stack)\n", SOCK_MAXADDRLEN); printf(" pathbuf[pathlen] = '\\0' writes at stack offset %d\n", pathlen); printf(" => stack OOB write of ~%lu bytes + neg-index write\n", as_size_t); printf("\nConclusion: code-path is genuinely vulnerable to sun_len<pathoff;\n"); printf(" reachable only via a kernel-produced malformed sockaddr.\n"); printf(" Fix: clamp pathlen to 0 when sun_len<pathoff (see fix.diff).\n"); return 0; } |