DragonFlyBSD Kernel Audit
DF-0663 / df0663_slist_sim.c
← back to finding ↓ download raw
/*
 * DF-0663 - SLIST_REMOVE on never-inserted element (ng_device newhook).
 *
 * Standalone userspace demonstration that the SLIST_REMOVE macro from
 * sys/sys/queue.h:208-220 panics (NULL deref) when invoked on an element
 * that was never inserted into the list. This reproduces the exact logic
 * of `ng_device_newhook` error paths at sys/netgraph/ng_device.c:290 and
 * :300 without needing to build/load the dead ng_device.c file.
 *
 * Build: cc -O2 -o df0663_slist_sim df0663_slist_sim.c
 * Run:   ./df0663_slist_sim
 * Expected: "Segmentation fault" (SIGSEGV) when SLIST_REMOVE walks off
 *           the end of the list and dereferences NULL->field.
 */

#include <sys/queue.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct ngd_connection {
    int unit;
    SLIST_ENTRY(ngd_connection) links;
};

struct head {
    struct ngd_connection *slh_first;
};

#define TEST_NAME "ng_device newhook SLIST_REMOVE on non-member"

int main(void) {
    /* Case 1: empty list. ng_device_newhook's first call where make_dev or
     * readq kmalloc fails. */
    struct head sc_head_empty = { NULL };
    struct ngd_connection *new_conn = malloc(sizeof(*new_conn));
    memset(new_conn, 0, sizeof(*new_conn));

    printf("[%s] Case 1: empty list, SLIST_REMOVE on never-inserted conn\n",
           TEST_NAME);
    printf("  SLIST_FIRST(&sc_head_empty) = %p\n",
           (void *)sc_head_empty.slh_first);
    printf("  new_conn = %p\n", (void *)new_conn);
    printf("  invoking SLIST_REMOVE(&sc_head_empty, new_conn, ...)\n");
    fflush(stdout);

    /* This macro: curelm = SLIST_FIRST(head) = NULL; while-loop body
     * derefs curelm->links.sle_next => NULL deref => SIGSEGV. */
    SLIST_REMOVE(&sc_head_empty, new_conn, ngd_connection, links);

    printf("  unreachable -- should have crashed\n");
    return 0;
}