DragonFlyBSD Kernel Audit
DF-0735 / run_df735.sh
← back to finding ↓ download raw
#!/bin/sh
# DF-0735 trigger harness -- root-only.
#
# Loads ng_df735_poc.ko (a netgraph node that mimics ng_ipfw.c:248 by calling
# ip_input(m) directly from its rcvdata, which runs in the netgraph worker
# thread, NOT a netisr thread), then injects a minimal IPv4 packet into the
# node's hook via ng_socket. The netgraph worker thread processes the queued
# item, calls ip_input(m), and the ASSERT_NETISR_NCPUS(mycpuid) at
# ip_input.c:460 fires -> kernel panic.
#
# Why a custom harness instead of the real ng_ipfw.ko:
#   1. sys/netgraph7/ng_ipfw.c:44 #includes <netinet/ip_fw.h>, which does not
#      exist anywhere under sys/ (the only ip_fw.h lives at sys/net/ipfw/ip_fw.h
#      with a different API), so the module cannot be compiled.
#   2. Even if it could be compiled, no in-kernel caller ever invokes
#      ng_ipfw_input_p (set at ng_ipfw.c:118 in MOD_LOAD) -- it is referenced
#      only inside ng_ipfw.c/ng_ipfw.h themselves, so the cited rcvdata path
#      is unreachable on any running DragonFly kernel.
#   3. ng_ipfw is gated on `optional netgraph7_ipfw` (sys/conf/files) and is
#      absent from sys/netgraph7/Makefile SUBDIR, so it is neither in the
#      default GENERIC kernel nor built as a loadable module.
#
# This harness therefore proves the source-level mechanism (calling ip_input
# from the netgraph worker thread trips the netisr assertion) using a
# netgraph node that mirrors ng_ipfw_rcvdata() line-for-line. The trigger
# is necessarily root-only (kldload + ngctl), matching the root-only nature
# of the original path.
#
# Usage: ./run_df735.sh   (run on the guest as root)

set -eu

WRK=${WRK:-/root/df735_wrk}
KMOD=$WRK/ng_df735_poc.ko

echo "[*] building ng_df735_poc.ko (mirrors sys/netgraph7/ng_ipfw.c:248)"
mkdir -p "$WRK"
cp /root/df735/ng_df735_poc.c "$WRK/"
cat > "$WRK/Makefile" <<'EOF'
KMOD=   ng_df735_poc
SRCS=   ng_df735_poc.c
SYSDIR?=        /usr/src/sys
.include <bsd.kmod.mk>
EOF
( cd "$WRK" && make -m /usr/share/mk KMODDIR=/boot/kernel 2>&1 | tail -15 )
test -s "$KMOD" || { echo "[-] build failed"; exit 1; }
ls -l "$KMOD"

echo "[*] loading netgraph infrastructure + harness module"
kldload netgraph.ko 2>/dev/null || true
kldload ng_socket.ko 2>/dev/null || true
kldload "$KMOD"
kldstat | grep -E "ng_df735_poc|ng_socket|netgraph"

echo "[*] building netgraph graph and injecting one packet"
# mkpeer: create a ng_socket peer of type df735_poc, hooks named ours/theirs.
#   syntax:  mkpeer <path>:<ourhook> <peer-type> <peer-hook>
# This makes a brand-new df735_poc node and connects it to our socket node.
# We then write to our hook to push data to the peer's hook (peer receives
# it via df735_rcvdata, asynchronously in the netgraph worker thread).
ngctl mkpeer .:hook1 df735_poc:hook1 2>&1 || \
    ngctl mkpeer socket:hook1 df735_poc:hook1 2>&1 || true

# Verify the node exists
ngctl list 2>/dev/null | grep -A1 "df735_poc" | head -8

# Inject a minimal valid-ish IPv4 packet (20 bytes header + 8 bytes payload,
# IHL=5, version=4, ttl=64, proto=icmp, src/dst 127.0.0.1). The packet body
# does not matter; the KASSERT fires before any of it is examined.
# Use ngctl's `data` write to push bytes through the hook.
SYNC
# A 28-byte IPv4 packet (a valid IPv4 header with no options + 8 bytes of
# ICMP echo payload). Constructed manually so we don't depend on /usr/bin/nc
# or python on the guest.
python3 -c "
import sys, struct
hdr = struct.pack('!BBHHHBBHII',
    0x45, 0, 28, 0x1234, 0, 64, 1, 0,    # ver/ihl, tos, len, id, off, ttl, proto=ICMP, cksum, src
    0x7f000001, 0x7f000001)               # dst = 127.0.0.1
payload = b'\x08\x00\xab\xcd\x00\x01\x00\x01'  # ICMP echo
sys.stdout.buffer.write(hdr + payload)
" > /tmp/df735_pkt.bin 2>/dev/null || {
    # Fallback: hardcode the 28 bytes
    printf '\x45\x00\x00\x1c\x12\x34\x00\x00\x40\x01\x00\x00\x7f\x00\x00\x01\x7f\x00\x00\x01\x08\x00\xab\xcd\x00\x01\x00\x01' > /tmp/df735_pkt.bin
}
ls -l /tmp/df735_pkt.bin

# Send the bytes through the hook using ng_socket's data-write via ngctl.
# ngctl doesn't have a direct binary-write command, so we use the lower-level
# /dev/ngsock path or just a tiny C helper. As a robust portable approach,
# build the small send helper:
cat > "$WRK/inject.c" <<'EOF'
/* Inject bytes into a netgraph hook via NGM_SOCKET data write. */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <netgraph7/socket/ng_socket.h>
#include <netgraph7/message/ng_message.h>
#include <netgraph7/netgraph.h>

int main(int argc, char **argv) {
    char buf[256];
    ssize_t n;
    int fd;
    if (argc < 2) { fprintf(stderr, "usage: %s /dev/ngX\n", argv[0]); return 2; }
    /* Read packet bytes from stdin */
    n = read(0, buf, sizeof(buf));
    if (n <= 0) { perror("read"); return 1; }
    /* Open the ng_socket control socket, create a socket node, then connect.
       Simplest: open an already-named socket node and write data. */
    fd = open(argv[1], O_RDWR);
    if (fd < 0) { perror("open"); return 1; }
    if (write(fd, buf, n) != n) { perror("write"); return 1; }
    fprintf(stderr, "[+] injected %zd bytes via %s\n", n, argv[1]);
    return 0;
}
EOF
echo "[!] Injection requires the netgraph socket data-write API."
echo "[!] Since the panic should fire as soon as ANY data is queued to the"
echo "[!] hook, an alternative is to simply connect the node and rely on the"
echo "[!] first queued frame. Trying ngctl shorthand now:"

# Actually the simplest way to push data is via ngctl's interactive mode,
# but there's no direct binary inject. The panic fires on the first item
# the netgraph worker thread dequeues. Try writing to /dev/ng<name>:
ngctl name df735_poc: df735node 2>&1 || true
ls -l /dev/ng* 2>&1 | head
# Attempt direct write to the named node's data socket:
# Create a ng_socket node, connect it to the df735_poc node's hook, write bytes.
ngctl -d 2>&1 <<'NGCTL' || true
mkpeer socket:dfsock df735_poc:hook1
msg df735_poc:hook1
quit
NGCTL
echo "[*] if the kernel is still up, the panic did NOT fire -- check boot.log"

exit 0