DF-5759 / bt_ssend_leak_kld.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 | /* * bt_ssend_leak_kld.c - production-code KLD rig for DF-5759 * * rfcomm_ssend (sys/netbt/rfcomm_socket.c:426-451) violates the pru_send * mbuf-ownership contract: on the m_copym(M_NOWAIT)-failure path it replies * ENOMEM without freeing (or queueing) the caller's mbuf chain. The generic * sosend() clears its `top` pointer unconditionally after so_pru_send() * (sys/kern/uipc_socket.c:924-930: "sent or freed in lower layer"), so the * chain is orphaned -> permanent kernel mbuf leak. * * This rig drives the REAL production path with no sys/ modifications: * 1. socreate() a real RFCOMM socket (pru_attach -> so_pcb = rfcomm_dlc) * 2. build a 1025-mbuf chain (m_len=MLEN so m_cat() must JOIN, not splat) * 3. drain the global mbuf objcache with m_get(M_NOWAIT) * 4. so_pru_send() the chain -> rfcomm_ssend -> m_copym fails -> ENOMEM * 5. release the drain; if the chain was leaked, mbufs-in-use stays +1025 * * Evidence: kprintf trace in dmesg + netstat -m delta from run.sh + * kern.ipc.mbstat m_mcfail delta (m_copym failure counter). * * Build: see build.sh (against /usr/src/sys in-guest) * Run: kldload ./bt_ssend_leak_kld.ko (root); dmesg | tail */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/malloc.h> #include <sys/mbuf.h> #include <sys/socket.h> #include <sys/socketvar.h> #include <sys/proc.h> #include <sys/thread.h> #include <sys/msgport2.h> #include <sys/socketops.h> #include <netbt/bluetooth.h> #define CHAIN_N 1024 /* payload mbufs + 1 pkthdr head */ #define DRAIN_CAP 200000 static int btssend_load(void) { struct socket *so = NULL; struct mbuf *chain, *n, **held; struct thread *td = curthread; int error, i, nheld = 0; /* 1. real socket: socreate runs rfcomm_sattach -> so_pcb = dlc */ error = socreate(AF_BLUETOOTH, &so, SOCK_STREAM, BTPROTO_RFCOMM, td); if (error) { kprintf("btssend: socreate failed: %d\n", error); return (error); } kprintf("btssend: socreate ok so=%p so_pcb=%p (rfcomm_dlc)\n", so, so->so_pcb); /* 2. build a long chain; m_len=MLEN forces m_cat to join, not splat */ chain = m_gethdr(M_WAITOK, MT_DATA); chain->m_len = MHLEN; for (i = 0; i < CHAIN_N; i++) { n = m_get(M_WAITOK, MT_DATA); n->m_len = MHLEN; m_cat(chain, n); } chain->m_pkthdr.len = MHLEN * (CHAIN_N + 1); kprintf("btssend: chain=%p pkthdr.len=%d (~%d mbufs)\n", chain, chain->m_pkthdr.len, CHAIN_N + 1); /* 3. drain the mbuf object cache (M_NOWAIT until failure) */ held = kmalloc(sizeof(*held) * DRAIN_CAP, M_TEMP, M_WAITOK | M_ZERO); while (nheld < DRAIN_CAP) { struct mbuf *t = m_get(M_NOWAIT, MT_DATA); if (t == NULL) break; held[nheld++] = t; } kprintf("btssend: drained %d mbufs; pool now empty\n", nheld); /* 4. production path: so_pru_send -> rfcomm_ssend -> m_copym fails */ error = so_pru_send(so, 0, chain, NULL, NULL, td); kprintf("btssend: so_pru_send -> %d (%s)\n", error, error == ENOMEM ? "ENOMEM - m_copym failed" : "unexpected"); /* 5. give the pool back; the chain must NOT come back if leaked */ for (i = 0; i < nheld; i++) m_free(held[i]); kfree(held, M_TEMP); /* 6. tear the socket down (rfcomm_sdetach -> rfcomm_detach -> kfree) */ soclose(so, 0); kprintf("btssend: done. if error==ENOMEM above, the %d-mbuf chain is " "leaked by rfcomm_ssend (DF-5759); expect netstat 'in use' " "to stay +%d\n", CHAIN_N + 1, CHAIN_N + 1); return (0); } static int btssend_unload(void) { return (0); } static int btssend_modevent(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: (void)btssend_load(); break; case MOD_UNLOAD: (void)btssend_unload(); break; default: return (EOPNOTSUPP); } return (0); } static moduledata_t btssend_mod = { "btssend", btssend_modevent, NULL }; DECLARE_MODULE(btssend, btssend_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); MODULE_DEPEND(btssend, netbt, 1, 1, 1); MODULE_VERSION(btssend, 1); |