DF-5747 / harness5747.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 | /* * harness5747.c -- KLD harness for DF-5747 (pass 2, sys/netbt/rfcomm_upper.c): * * rfcomm_bind() (sys/netbt/rfcomm_upper.c:103-109) performs NO DLC state * check -- it happily overwrites rd_laddr of a DLC that is already * LISTENING (or mid-connect). NetBSD upstream added * * if (dlc->rd_state != RFCOMM_DLC_CLOSED) * return EINVAL; * * to rfcomm_bind_pcb() (NetBSD rfcomm_upper.c rev 1.12+); DragonFly never * took that fix. Impact: an unprivileged local user can re-bind a live * listener, mutating its advertised channel/PSM underneath the listening * session (state confusion -- the listener allocation scan in * rfcomm_listen() and the best-match scan in rfcomm_dlc_newconn() both * key off rd_laddr.bt_channel). * * The production files rfcomm_session.c / rfcomm_dlc.c / rfcomm_upper.c are * compiled into this module VERBATIM (copied from /usr/src/sys/netbt by * build.sh, md5s recorded). L2CAP is stubbed (l2cap_stub.c). * * Fire with: sysctl hw.df5747_fire=1 (as root, inside the guest) * Expected: "[DF-5747] bind() while LISTEN returned 0" (BUG: upstream * returns EINVAL); listener channel mutated 3 -> 5; clean * teardown afterwards proves no crash, only wrong semantics. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/malloc.h> #include <sys/mbuf.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/sysctl.h> #include <sys/types.h> #include <netbt/bluetooth.h> #include <netbt/hci.h> #include <netbt/l2cap.h> #include <netbt/rfcomm.h> /* upper-layer (socket) callbacks -- log only, like rfcomm_socket.c would */ static void up_connecting(void *u) { kprintf("[upper] connecting\n"); } static void up_connected(void *u) { kprintf("[upper] connected\n"); } static void up_disconnected(void *u, int e) { kprintf("[upper] disconnected (%d)\n", e); } static void *up_newconn(void *u, struct sockaddr_bt *l, struct sockaddr_bt *r) { kprintf("[upper] newconn\n"); return NULL; } static void up_complete(void *u, int n) { kprintf("[upper] complete %d\n", n); } static void up_linkmode(void *u, int m) { kprintf("[upper] linkmode %d\n", m); } static void up_input(void *u, struct mbuf *m) { kprintf("[upper] input %d bytes\n", m->m_pkthdr.len); m_freem(m); } static const struct btproto upper_proto = { up_connecting, up_connected, up_disconnected, up_newconn, up_complete, up_linkmode, up_input, }; static int pool_ready; static int fire5747(SYSCTL_HANDLER_ARGS) { int v = 0, err; struct rfcomm_dlc *dlc = NULL; struct sockaddr_bt laddr; err = sysctl_handle_int(oidp, &v, 0, req); if (err != 0 || req->newptr == NULL) return err; if (v == 0) return 0; kprintf("=== DF-5747 harness: rfcomm_bind() missing DLC state check ===\n"); if (!pool_ready) { rfcomm_init(); /* credit pool (bt_proto.c normally) */ pool_ready = 1; } /* local app: socket(), bind(channel 3), listen() */ err = rfcomm_attach(&dlc, &upper_proto, NULL); KKASSERT(err == 0); rfcomm_rcvd(dlc, 4096); memset(&laddr, 0, sizeof(laddr)); laddr.bt_len = sizeof(laddr); laddr.bt_family = AF_BLUETOOTH; laddr.bt_psm = L2CAP_PSM_RFCOMM; laddr.bt_channel = 3; err = rfcomm_bind(dlc, &laddr); kprintf("[DF-5747] bind #1 (state CLOSED) -> %d (expect 0)\n", err); KKASSERT(err == 0); err = rfcomm_listen(dlc); kprintf("[DF-5747] listen() -> %d; state=%d (8=LISTEN), channel=%d\n", err, dlc->rd_state, dlc->rd_laddr.bt_channel); KKASSERT(err == 0); KKASSERT(dlc->rd_state == RFCOMM_DLC_LISTEN); /* * THE BUG: re-bind the LIVE listener (upstream: EINVAL). */ laddr.bt_channel = 5; /* arbitrary new channel */ laddr.bt_psm = 0x1001; /* arbitrary odd PSM */ err = rfcomm_bind(dlc, &laddr); kprintf("[DF-5747] bind #2 (state LISTEN) -> %d %s\n", err, (err == 0 ? "(BUG: live listener mutated; upstream returns EINVAL)" : "(EINVAL - fixed)")); kprintf("[DF-5747] listener now advertises: channel=%d psm=0x%04x state=%d\n", dlc->rd_laddr.bt_channel, dlc->rd_laddr.bt_psm, dlc->rd_state); /* * prove the mutated listener still tears down cleanly (no crash, * pure semantic bug) -- production close path, exactly as * rfcomm_sdetach() would drive it. */ err = rfcomm_disconnect(dlc, 0); kprintf("[DF-5747] disconnect() on LISTEN dlc -> %d (expect EINVAL)\n", err); rfcomm_detach(&dlc); kprintf("[DF-5747] detach() complete, dlc freed, listen session freed\n"); kprintf("=== DF-5747 harness done ===\n"); return 0; } SYSCTL_PROC(_hw, OID_AUTO, df5747_fire, CTLTYPE_INT | CTLFLAG_RW, 0, 0, fire5747, "I", "fire DF-5747 bind-no-state-check scenario"); static int harness_mode(module_t mod, int type, void *data) { return 0; } static moduledata_t harness_moddata = { "rfcomm_up5747", harness_mode, NULL }; DECLARE_MODULE(rfcomm_up5747, harness_moddata, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); |