DF-0735 / ng_df735_poc.c
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 144 145 146 147 148 149 150 151 | /* * ng_df735_poc.c - DF-0735 faithful demonstration harness. * * This is a minimal netgraph node that reproduces, line-for-line, the * wrong-context call in sys/netgraph7/ng_ipfw.c:248 (and :245). The * real ng_ipfw.c cannot be compiled on this DragonFly tree because its * `#include <netinet/ip_fw.h>` does not resolve (no such header exists * under sys/netinet/ -- ipfw.h lives at sys/net/ipfw/ip_fw.h with a * different API), and even if it could be compiled, no in-kernel caller * ever invokes its `ng_ipfw_input_p` function pointer (it is referenced * only inside ng_ipfw.c itself), so the cited rcvdata path is dead code * on any running DragonFly kernel. * * To prove the source-level mechanism is real (calling ip_input from * the netgraph worker thread trips the netisr assertion), this harness * implements a netgraph node whose rcvdata callback executes the exact * body of ng_ipfw_rcvdata() at lines 220-256: * * NGI_GET_M(item, m); * NG_FREE_ITEM(item); * ... * ip_input(m); <-- ng_ipfw.c:248, the cited bug * * The hook is created with NG_HOOK_FORCE_QUEUE (mirroring ng_ipfw.c:185 * ng_ipfw_connect), so the data is queued asynchronously and processed * by the netgraph worker thread created at ng_base.c:2787-2789 -- which * is NOT on the netreglist (only netmsg_service_port_init ports at * netisr.c:258-280 are). Therefore the ASSERT_NETISR_NCPUS(mycpuid) at * the top of ip_input (ip_input.c:460) fires on any default-GENERIC * kernel (INVARIANTS ON). * * Trigger (as root): * kldload ng_socket.ko * kldload ./ng_df735_poc.ko * ngctl mkpeer df735_poc: hook1 hook2 # creates a df735_poc node with hook "hook1" * ngctl name df735_poc:hook1 dfpoc # name the peer for easy access -- skip, simpler: * ngctl msg df735_poc: setver 0 # noop, just to verify the node is alive * # The simplest inject path: open the node's hook via ng_socket and write 28 bytes * # (a minimal IPv4 header) into it; the data is queued, the netgraph worker thread * # calls our rcvdata, which calls ip_input(m) -> panic. * * See run_df735.sh for the exact ngctl sequence. */ #include <sys/param.h> #include <sys/systm.h> #include <sys/kernel.h> #include <sys/mbuf.h> #include <sys/malloc.h> #include <sys/errno.h> #include <net/if.h> #include <netinet/in.h> #include <netinet/in_systm.h> #include <netinet/ip.h> #include <netinet/ip_var.h> #include <netgraph7/ng_message.h> #include <netgraph7/netgraph.h> /* ip_input() is defined at sys/netinet/ip_input.c:445 but not declared in * any public header. ng_ipfw.c gets an implicit declaration for the same * call (line 248); we declare it explicitly. */ extern void ip_input(struct mbuf *); /* Netgraph methods */ static ng_constructor_t df735_cons; static ng_newhook_t df735_newhook; static ng_connect_t df735_connect; static ng_rcvdata_t df735_rcvdata; static ng_disconnect_t df735_disconnect; /* Netgraph node type */ #define DF735_NODE_TYPE "df735_poc" #define DF735_HOOK_NAME "hook1" static struct ng_type df735_typestruct = { .version = NG_ABI_VERSION, .name = DF735_NODE_TYPE, .constructor = df735_cons, .newhook = df735_newhook, .connect = df735_connect, .rcvdata = df735_rcvdata, .disconnect = df735_disconnect, }; NETGRAPH_INIT(df735_poc, &df735_typestruct); static int df735_cons(node_p node) { return (0); } static int df735_newhook(node_p node, hook_p hook, const char *name) { if (strcmp(name, DF735_HOOK_NAME) != 0) return (EINVAL); return (0); } /* * Mirror ng_ipfw.c:182-187 ng_ipfw_connect: force queueing so the data * is processed asynchronously by the netgraph worker thread, NOT inline * by the sender. This is what makes the subsequent ip_input call run in * the wrong (netgraph) thread context. */ static int df735_connect(hook_p hook) { NG_HOOK_FORCE_QUEUE(hook); return (0); } /* * Faithful copy of ng_ipfw_rcvdata() at sys/netgraph7/ng_ipfw.c:220-256 * for the NG_IPFW_IN branch (line 248: ip_input(m)). * * We skip the m_tag NGM_IPFW_COOKIE lookup that ng_ipfw.c:228-232 does, * because that tag is only ever set by ng_ipfw_input() (the dead-code * ipfw callback at line 259) and is irrelevant to demonstrating the * wrong-context call. The KASSERT in ip_input fires before any of the * packet-body logic, so the packet contents do not matter either. */ static int df735_rcvdata(hook_p hook, item_p item) { struct mbuf *m; NGI_GET_M(item, m); NG_FREE_ITEM(item); /* * This is the cited bug -- ng_ipfw.c:248. The rcvdata runs in the * netgraph worker thread (ng_base.c:2787-2789), which is NOT a * netisr thread. ip_input.c:460 opens with ASSERT_NETISR_NCPUS -> * panic on default-GENERIC (INVARIANTS ON). */ ip_input(m); return (0); } static int df735_disconnect(hook_p hook) { if (NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0 && NG_NODE_IS_VALID(NG_HOOK_NODE(hook))) ng_rmnode_self(NG_HOOK_NODE(hook)); return (0); } |