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