DF-0735 / run_df735.sh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | #!/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 |