/*
 * Minimized reproducer: KASAN slab-use-after-free in dm_setup_md_queue
 * after concurrent DM_TABLE_LOAD destroy (7ccb76e6).
 *
 * Race:
 *   - Fail path: opens a backing device then fails table validation, unlocks
 *     md type lock, and dm_table_destroy() frees the table_device.
 *   - OK path: loads a table that does not touch that device (error),
 *     then dm_setup_md_queue() walks md->table_devices without holding
 *     table_devices_lock while the fail path frees an entry.
 *
 * dm_setup_md_queue() only runs while md->type == DM_TYPE_NONE, so each
 * iteration must CREATE + REMOVE the mapped device.
 */

#define _GNU_SOURCE
#include <fcntl.h>
#include <linux/dm-ioctl.h>
#include <linux/loop.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>

#define DM_CTL_PATH "/dev/mapper/control"
#define DEV0        "syz_dm0"
#define BUF_SIZE    8192
#define ALIGN8(x)   (((x) + 7) & ~7)
#define N_PROCS     6

static char loop_dev[64];

static void init_hdr(struct dm_ioctl *hdr, const char *name)
{
	memset(hdr, 0, sizeof(*hdr));
	hdr->version[0] = 4;
	hdr->version[1] = 48;
	hdr->version[2] = 0;
	hdr->data_size = BUF_SIZE;
	hdr->data_start = sizeof(struct dm_ioctl);
	if (name)
		strncpy(hdr->name, name, DM_NAME_LEN - 1);
}

static int setup_loop_backing(void)
{
	int ctl, loop, backing;
	char loop_path[32];

	backing = open("/tmp/dm_repro_backing", O_RDWR | O_CREAT | O_TRUNC, 0644);
	if (backing < 0)
		return -1;
	if (ftruncate(backing, 4096 * 8192) < 0)
		return -1;

	ctl = open("/dev/loop-control", O_RDWR);
	if (ctl < 0)
		return -1;
	loop = ioctl(ctl, LOOP_CTL_GET_FREE);
	close(ctl);
	if (loop < 0)
		return -1;

	snprintf(loop_path, sizeof(loop_path), "/dev/loop%d", loop);
	loop = open(loop_path, O_RDWR);
	if (loop < 0)
		return -1;
	if (ioctl(loop, LOOP_SET_FD, backing) < 0)
		return -1;
	close(loop);
	close(backing);

	snprintf(loop_dev, sizeof(loop_dev), "%s", loop_path);
	return 0;
}

/* Opens loop device, then fails on missing 2nd target (overlap/next=0). */
static int table_load_fail(int fd, const char *name)
{
	char buf[BUF_SIZE];
	struct dm_ioctl *hdr = (struct dm_ioctl *)buf;
	struct dm_target_spec *spec;
	char *params;
	int params_len;

	memset(buf, 0, sizeof(buf));
	init_hdr(hdr, name);
	hdr->target_count = 2;

	spec = (struct dm_target_spec *)(buf + sizeof(struct dm_ioctl));
	spec->sector_start = 0;
	spec->length = 2048;
	spec->next = 0;
	strncpy(spec->target_type, "linear", DM_MAX_TYPE_NAME - 1);

	params = (char *)(spec + 1);
	snprintf(params, 128, "%s 0", loop_dev);
	params_len = strlen(params) + 1;

	hdr->data_size = sizeof(struct dm_ioctl) +
			 sizeof(struct dm_target_spec) + ALIGN8(params_len);
	return ioctl(fd, DM_TABLE_LOAD, hdr);
}

/* Valid load with no backing device → dm_setup_md_queue(). */
static int table_load_ok(int fd, const char *name)
{
	char buf[BUF_SIZE];
	struct dm_ioctl *hdr = (struct dm_ioctl *)buf;
	struct dm_target_spec *spec;

	memset(buf, 0, sizeof(buf));
	init_hdr(hdr, name);
	hdr->target_count = 1;

	spec = (struct dm_target_spec *)(buf + sizeof(struct dm_ioctl));
	spec->sector_start = 0;
	spec->length = 2048;
	spec->next = 0;
	strncpy(spec->target_type, "error", DM_MAX_TYPE_NAME - 1);

	hdr->data_size = sizeof(struct dm_ioctl) +
			 sizeof(struct dm_target_spec) + ALIGN8(1);
	((char *)(spec + 1))[0] = '\0';
	return ioctl(fd, DM_TABLE_LOAD, hdr);
}

struct race_arg {
	int role; /* 0 = fail, 1 = ok */
	pthread_barrier_t *bar;
};

static void *race_thread(void *arg)
{
	struct race_arg *ra = arg;
	int fd = open(DM_CTL_PATH, O_RDWR);

	if (fd < 0)
		return NULL;

	pthread_barrier_wait(ra->bar);
	if (ra->role == 0)
		(void)table_load_fail(fd, DEV0);
	else
		(void)table_load_ok(fd, DEV0);

	if (ra->role == 0)
		(void)table_load_fail(fd, DEV0);
	else
		(void)table_load_ok(fd, DEV0);

	close(fd);
	return NULL;
}

static void run_once(void)
{
	char buf[BUF_SIZE];
	struct dm_ioctl *hdr = (struct dm_ioctl *)buf;
	pthread_t t0, t1;
	pthread_barrier_t bar;
	struct race_arg a0 = { .role = 0, .bar = &bar };
	struct race_arg a1 = { .role = 1, .bar = &bar };
	int fd;

	fd = open(DM_CTL_PATH, O_RDWR);
	if (fd < 0)
		return;

	init_hdr(hdr, DEV0);
	ioctl(fd, DM_DEV_CREATE, hdr);

	pthread_barrier_init(&bar, NULL, 2);
	pthread_create(&t0, NULL, race_thread, &a0);
	pthread_create(&t1, NULL, race_thread, &a1);
	pthread_join(t0, NULL);
	pthread_join(t1, NULL);
	pthread_barrier_destroy(&bar);

	init_hdr(hdr, DEV0);
	ioctl(fd, DM_DEV_REMOVE, hdr);
	close(fd);
}

int main(int argc, char **argv)
{
	int i;

	if (argc > 1)
		snprintf(loop_dev, sizeof(loop_dev), "%s", argv[1]);
	else if (setup_loop_backing() < 0)
		return 1;

	for (i = 0; i < N_PROCS; i++) {
		if (fork() == 0) {
			while (1)
				run_once();
		}
	}

	for (;;)
		sleep(3600);
}
