/*
 * DF-2559 -- SO_PASSCRED SCM_CREDS auto-creation path in uipc_send
 * (uipc_usrreq.c:693-698): the return of sbcreatecontrol() (M_NOWAIT)
 * is never checked; under mbuf exhaustion it returns NULL and
 * unp_internalize(NULL) dereferences mtod(NULL,...) -> kernel panic.
 *
 * Rig: sysctl kern.dfv2559 = N pins ALL mbufs except N, then a local
 * unprivileged sender triggers the SOCK_DGRAM + SO_PASSCRED path.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/sysctl.h>
#include <sys/mbuf.h>
#include <sys/malloc.h>

static struct mbuf **df_held;
static int df_nheld;

static int dfv2559_backing;

static int
dfv2559_sysctl(SYSCTL_HANDLER_ARGS)
{
	int error, v = 0;
	int i, budget = 4000000;

	if (req->newptr != NULL) {
		v = dfv2559_backing;
		error = sysctl_handle_int(oidp, &v, 0, req);
		if (error)
			return error;
		dfv2559_backing = v;
		if (v < -1 || v > 64)
			return EINVAL;
		/* release previous pins */
		for (i = 0; i < df_nheld; i++)
			m_free(df_held[i]);
		df_nheld = 0;
		if (v == -1)
			return 0;		/* just release */
		/* pin everything we can */
		while (budget--) {
			struct mbuf *m = m_get(M_NOWAIT, MT_DATA);
			if (m == NULL)
				break;
			df_held[df_nheld++] = m;
			if (df_nheld == 3900000)
				break;
		}
		kprintf("dfv2559: pinned %d mbufs; now releasing %d\n",
		    df_nheld, v);
		/* release v mbufs back to the free pool */
		for (i = 0; i < v && df_nheld > 0; i++)
			m_free(df_held[--df_nheld]);
		return 0;
	}
	return sysctl_handle_int(oidp, &v, 0, req);
}
SYSCTL_PROC(_kern, OID_AUTO, dfv2559, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, dfv2559_sysctl, "I", "pin all mbufs minus N (-1 releases)");

static int
dfv2559_load(module_t mod, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		df_held = kmalloc(sizeof(*df_held) * 4000000, M_TEMP,
		    M_WAITOK);
		kprintf("dfv2559: loaded\n");
		return 0;
	case MOD_UNLOAD:
		kfree(df_held, M_TEMP);
		return 0;
	default:
		return 0;
	}
}
static moduledata_t dfv2559_mod = { "dfv2559", dfv2559_load, NULL };
DECLARE_MODULE(dfv2559, dfv2559_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
