/*
 * DF-4467 PoC: software XTS per-session shared tweak race.
 *
 * cryptoapi's software xts (aes_xts_crypt / twofish / serpent) keeps the
 * running tweak in the SESSION ctx (aes_xts_ctx.tweak) and reinits it per
 * crypt call.  dm_target_crypt calls cryptoapi_cipher_crypt() on the SAME
 * session concurrently from multiple CPUs (write encryption runs in the
 * strategy context of whichever CPU dispatched the bio), so two
 * dmtc_bio_encdec loops interleave aes_xts_reinit()/crypt_block_xts()
 * mutations of ctx->tweak -> wrong tweaks -> silent ciphertext corruption.
 *
 * Round-trip test: writers write known distinct patterns to their own
 * blocks through the crypt device; afterwards we read every block back
 * (also through the crypt device).  Any block that does not round-trip
 * is corrupted.  Single-threaded control run must always round-trip.
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>

struct plistref {
	void   *pref_plist;
	size_t  pref_len;
};
#define DM_IOCTL_CMD 0
#define NETBSD_DM_IOCTL _IOWR(0xfd, DM_IOCTL_CMD, struct plistref)

static int ctlfd = -1;

static int
dm_ioctl_xml(const char *xml)
{
	struct plistref pref;
	char *buf;
	int error;

	buf = strdup(xml);
	pref.pref_plist = buf;
	pref.pref_len = strlen(buf) + 1;
	error = ioctl(ctlfd, NETBSD_DM_IOCTL, &pref);
	free(buf);
	return (error);
}

static const char *KEY =
	"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";

static void
dm_create_load_resume(const char *name, const char *type, uint64_t len,
    const char *params)
{
	char x[1024];

	snprintf(x, sizeof(x),
	    "<plist><dict><key>command</key><string>create</string>"
	    "<key>version</key><array><integer>0x4</integer>"
	    "<integer>0x10</integer><integer>0x0</integer></array>"
	    "<key>name</key><string>%s</string>"
	    "<key>flags</key><integer>0x0</integer></dict></plist>", name);
	if (dm_ioctl_xml(x) != 0) { printf("create %s failed\n", name); exit(1); }
	snprintf(x, sizeof(x),
	    "<plist><dict><key>command</key><string>reload</string>"
	    "<key>version</key><array><integer>0x4</integer>"
	    "<integer>0x10</integer><integer>0x0</integer></array>"
	    "<key>name</key><string>%s</string>"
	    "<key>flags</key><integer>0x0</integer>"
	    "<key>cmd_data</key><array><dict>"
	    "<key>type</key><string>%s</string>"
	    "<key>start</key><integer>0x0</integer>"
	    "<key>length</key><integer>0x%llx</integer>"
	    "<key>params</key><string>%s</string>"
	    "</dict></array></dict></plist>", name, type,
	    (unsigned long long)len, params);
	if (dm_ioctl_xml(x) != 0) { printf("load %s failed\n", name); exit(1); }
	snprintf(x, sizeof(x),
	    "<plist><dict><key>command</key><string>resume</string>"
	    "<key>version</key><array><integer>0x4</integer>"
	    "<integer>0x10</integer><integer>0x0</integer></array>"
	    "<key>name</key><string>%s</string>"
	    "<key>flags</key><integer>0x0</integer></dict></plist>", name);
	if (dm_ioctl_xml(x) != 0) { printf("resume %s failed\n", name); exit(1); }
}

#define NBLOCKS 512			/* 4KB blocks */
#define NTHREADS 8

static int devfd;
static char *expected[NBLOCKS];
static volatile int pattern_gen[NBLOCKS];
static volatile int g_stop;

static void
fill_pattern(char *buf, int gen, int block)
{
	int i;
	for (i = 0; i < 4096; i += sizeof(unsigned))
		*(unsigned *)(buf + i) = (gen << 24) ^ (block << 16) ^ i;
}

static void *
writer(void *arg)
{
	long id = (long)arg;
	char *buf;
	int b, gen = 1;

	buf = aligned_alloc(4096, 4096);
	int lo = (int)id * (NBLOCKS / NTHREADS);
	int hi = lo + (NBLOCKS / NTHREADS);

	while (!g_stop) {
		/* each writer OWNS a disjoint block range (no false races);
		 * concurrency on the shared session is what we want */
		for (b = lo; b < hi; b++) {
			fill_pattern(buf, gen, b);
			if (pwrite(devfd, buf, 4096, (off_t)b * 4096) != 4096)
				break;
			pattern_gen[b] = gen;
		}
		gen++;
	}
	free(buf);
	return (NULL);
}

int
main(int argc, char **argv)
{
	const char *pdev = argc > 1 ? argv[1] : "/dev/vn0";
	int seconds = argc > 2 ? atoi(argv[2]) : 20;
	int control = argc > 3 ? atoi(argv[3]) : 0;
	pthread_t th[NTHREADS];
	char params[512];
	char *buf;
	int i, b, bad, rounds;

	ctlfd = open("/dev/mapper/control", O_RDWR);
	if (ctlfd < 0) { perror("control"); return 1; }

	snprintf(params, sizeof(params),
	    "aes-xts-plain64 %s 0 %s 0", KEY, pdev);
	dm_create_load_resume("xt0", "crypt", 131072, params);
	devfd = open("/dev/mapper/xt0", O_RDWR);
	if (devfd < 0) { perror("open xt0"); return 1; }

	for (b = 0; b < NBLOCKS; b++) {
		expected[b] = malloc(4096);
		fill_pattern(expected[b], 0, b);
		pattern_gen[b] = 0;
		if (pwrite(devfd, expected[b], 4096, (off_t)b * 4096) != 4096)
			{ perror("initial pwrite"); return 1; }
	}
	printf("[setup] %d blocks initialized (single threaded)\n", NBLOCKS);

	if (!control) {
		for (i = 0; i < NTHREADS; i++)
			pthread_create(&th[i], NULL, writer, (void *)(long)i);
		sleep(seconds);
		g_stop = 1;
		for (i = 0; i < NTHREADS; i++)
			pthread_join(th[i], NULL);
		printf("[race] %d writers stopped after %ds\n", NTHREADS,
		    seconds);
	}

	/* verify: read each block 3x, decrypt must equal expected pattern */
	buf = aligned_alloc(4096, 4096);
	bad = 0;
	for (rounds = 0; rounds < 3; rounds++) {
		for (b = 0; b < NBLOCKS; b++) {
			if (pread(devfd, buf, 4096, (off_t)b * 4096) != 4096) {
				perror("verify pread");
				return 1;
			}
			fill_pattern(expected[b], pattern_gen[b], b);
			if (memcmp(buf, expected[b], 4096) != 0) {
				bad++;
				if (bad <= 4)
					printf("[CORRUPT] block %d round %d: "
					    "got %02x%02x%02x%02x want "
					    "%02x%02x%02x%02x\n", b, rounds,
					    buf[0], buf[1], buf[2], buf[3],
					    expected[b][0], expected[b][1],
					    expected[b][2], expected[b][3]);
			}
		}
	}
	printf("[result] corrupted blocks: %d / %d (%s)\n", bad, NBLOCKS,
	    bad ? "XTS TWEAK RACE REPRODUCED" : "no corruption detected");
	return (bad ? 3 : 0);
}
