/*
 * kv.c — /dev/kmem read/write + arbitrary-kernel-function-call facility
 * for the DF-5051 / DF-5052 dal_vector runtime PoCs.
 *
 * Root-only tool. It:
 *   1. writes a small trampoline into a dead kernel code cave (sys_sbrk's
 *      body — sbrk is not used on this system),
 *   2. re-points sysent[SYS_sbrk].sy_call at it,
 *   3. lets userland invoke any kernel address with up to six controlled
 *      register arguments; the trampoline tail-stores the function's
 *      return value into sysmsg->sm_result (offset 0) and returns error 0,
 *   4. restores the original sysent entry on exit.
 *
 * usage:
 *   kv setup <cavehex> <sysenthex> <slot>          install
 *   kv read  <hexaddr> <len>                       dump hex to stdout
 *   kv write <hexaddr> <hexbytes>                  poke memory
 *   kv call  <fnhex> [a1 .. a6]                    invoke (hex args)
 *   kv restore                                      undo sysent patch
 *
 * NOTE: this is PoC scaffolding, not part of the vulnerability claim: the
 * target functions (dal_vector_*) are latent-library defects with no
 * in-tree misusing caller, so ANY runtime demonstration must call them
 * out-of-context. The trampoline merely plays the role of a hypothetical
 * misusing caller, with fully controlled arguments.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/syscall.h>

/*
 * Call vehicle slot: SYS_lpathconf (533). Nothing in the base system
 * invokes this syscall, so patching it cannot hijack an unrelated
 * process (SYS_sbrk turned out to be actively used by cc/libc).
 */
#define	KV_SLOT		534	/* obsolete vmm_guest_ctl: unassigned, nosys */

static int kmemfd = -1;

static int
kopen(void)
{
	if (kmemfd < 0) {
		kmemfd = open("/dev/kmem", O_RDWR);
		if (kmemfd < 0) {
			perror("open /dev/kmem");
			return (-1);
		}
	}
	return (0);
}

static int
kread(unsigned long long addr, void *buf, size_t len)
{
	if (lseek(kmemfd, (off_t)addr, SEEK_SET) == (off_t)-1) {
		perror("lseek");
		return (-1);
	}
	if (read(kmemfd, buf, len) != (ssize_t)len) {
		fprintf(stderr, "kread: short read at %llx\n", addr);
		return (-1);
	}
	return (0);
}

static int
kwrite(unsigned long long addr, const void *buf, size_t len)
{
	if (lseek(kmemfd, (off_t)addr, SEEK_SET) == (off_t)-1) {
		perror("lseek");
		return (-1);
	}
	if (write(kmemfd, buf, len) != (ssize_t)len) {
		fprintf(stderr, "kwrite: short write at %llx\n", addr);
		return (-1);
	}
	return (0);
}

/*
 * Trampoline bytes (see header comment):
 *   push rbp; mov rbp,rsp; push rdi
 *   mov rax,rsi; mov r11,[rax]; mov rax,[rax+8]
 *   mov rdi,[rax]; mov rsi,[rax+8]; mov rdx,[rax+16]
 *   mov rcx,[rax+24]; mov r8,[rax+32]; mov r9,[rax+40]
 *   call r11
 *   pop rdx; mov [rdx],rax; xor eax,eax; pop rbp; ret
 */
/*
 * Magic-guarded call trampoline (assembled by hand, x86_64):
 *   push rbp; mov rbp,rsp; push rdi(sysmsg)
 *   mov rax,[rsi+16]                 ; argp[2] = magic
 *   mov rdx, 0x5151c0de5151c0de
 *   cmp rdx,rax ; jne enosys         ; refuse non-kv callers
 *   mov rax,rsi
 *   mov r11,[rax]                    ; argp[0] = fn
 *   mov rax,[rax+8]                  ; argp[1] = args block
 *   mov rdi..r9, [rax+0..40]         ; up to six arguments
 *   call r11
 *   pop rdi ; mov [rdi],rax          ; sysmsg_result = retval
 *   xor eax,eax ; pop rbp ; ret
 * enosys:
 *   pop rdi ; mov eax,78(ENOSYS) ; pop rbp ; ret
 */
#define	KV_MAGIC	0x5151c0de5151c0deULL

static const unsigned char tramp[] = {
	0x55,
	0x48, 0x89, 0xe5,
	0x57,
	0x48, 0x8b, 0x46, 0x10,			/* mov rax,[rsi+16] */
	0x48, 0xba, 0xde, 0xc0, 0x51, 0x51,	/* mov rdx,MAGIC */
	0xde, 0xc0, 0x51, 0x51,
	0x48, 0x39, 0xc2,			/* cmp rdx,rax */
	0x75, 0x2c,				/* jne enosys */
	0x48, 0x89, 0xf0,			/* mov rax,rsi */
	0x4c, 0x8b, 0x18,			/* mov r11,[rax] */
	0x48, 0x8b, 0x40, 0x08,			/* mov rax,[rax+8] */
	0x48, 0x8b, 0x38,			/* mov rdi,[rax] */
	0x48, 0x8b, 0x70, 0x08,			/* mov rsi,[rax+8] */
	0x48, 0x8b, 0x50, 0x10,			/* mov rdx,[rax+16] */
	0x48, 0x8b, 0x48, 0x18,			/* mov rcx,[rax+24] */
	0x4c, 0x8b, 0x40, 0x20,			/* mov r8,[rax+32] */
	0x4c, 0x8b, 0x48, 0x28,			/* mov r9,[rax+40] */
	0x41, 0xff, 0xd3,			/* call r11 (d3, NOT d2=r10) */
	0x5f,					/* pop rdi */
	0x48, 0x89, 0x07,			/* mov [rdi],rax */
	0x31, 0xc0,				/* xor eax,eax */
	0x5d,					/* pop rbp */
	0xc3,					/* ret */
	/* enosys: */
	0x5f,					/* pop rdi */
	0xb8, 0x4e, 0x00, 0x00, 0x00,		/* mov eax,78 */
	0x5d,					/* pop rbp */
	0xc3					/* ret */
};

/*
 * Diagnostic trampoline: log rdi/rsi and argp[0..2] to cave+0x70,
 * return 0 without calling anything. Imm32 displacements are
 * sign-extended, so kernel addresses encode as their low 32 bits.
 * (LOG area at cave+0x70.)
 */
static const unsigned char diagtramp_tmpl[] = {
	0x55,
	0x48, 0x89, 0xe5,
	0x48, 0x89, 0x3c, 0x25, 0, 0, 0, 0,	/* mov [LOG+0], rdi */
	0x48, 0x89, 0x34, 0x25, 0, 0, 0, 0,	/* mov [LOG+8], rsi */
	0x48, 0x8b, 0x06,			/* mov rax,[rsi] */
	0x48, 0x89, 0x04, 0x25, 0, 0, 0, 0,	/* mov [LOG+16], rax */
	0x48, 0x8b, 0x46, 0x08,			/* mov rax,[rsi+8] */
	0x48, 0x89, 0x04, 0x25, 0, 0, 0, 0,	/* mov [LOG+24], rax */
	0x48, 0x8b, 0x46, 0x10,			/* mov rax,[rsi+16] */
	0x48, 0x89, 0x04, 0x25, 0, 0, 0, 0,	/* mov [LOG+32], rax */
	0x31, 0xc0,
	0x5d,
	0xc3
};

/*
 * Defaults for this kernel build (X86_64_GENERIC, ISO
 * 20260629-DEV-v6.5.0-1712): cave = sys_sbrk body (dead code),
 * sysent table. Overridable via kv setup.
 */
static unsigned long long cave_addr = 0xffffffff809bda90ULL;
static unsigned long long sysent_addr = 0xffffffff810cb880ULL;
static unsigned long long slot_no;
static unsigned char orig_entry[24];
static int patched = 0;

static void
patch_imm32(unsigned char *code, size_t off, unsigned long long addr)
{
	unsigned int v = (unsigned int)(addr & 0xffffffffu);

	code[off + 0] = (unsigned char)(v);
	code[off + 1] = (unsigned char)(v >> 8);
	code[off + 2] = (unsigned char)(v >> 16);
	code[off + 3] = (unsigned char)(v >> 24);
}

static int
do_setup(const char *cave, const char *sysent, const char *slot)
{
	unsigned long long entry;
	unsigned char narg[4] = { 2, 0, 0, 0 };

	cave_addr = strtoull(cave, NULL, 16);
	sysent_addr = strtoull(sysent, NULL, 16);
	slot_no = strtoull(slot, NULL, 0);
	entry = sysent_addr + slot_no * 24;

	if (kopen() < 0)
		return (1);
	if (kread(entry, orig_entry, sizeof(orig_entry)) < 0)
		return (1);
	fprintf(stderr, "kv: sysent[%llu] @ %llx: narg=%d call=%llx\n",
	    slot_no, entry, *(int *)(void *)orig_entry,
	    *(unsigned long long *)(void *)(orig_entry + 8));

	if (kwrite(cave_addr, tramp, sizeof(tramp)) < 0)
		return (1);
	if (kwrite(entry, narg, 4) < 0)
		return (1);
	if (kwrite(entry + 8, &cave_addr, 8) < 0)
		return (1);
	patched = 1;
	fprintf(stderr, "kv: trampoline %zu bytes at %llx; sysent[%llu] "
	    "patched\n", sizeof(tramp), cave_addr, slot_no);
	return (0);
}

static int
do_diag(const char *cave)
{
	unsigned char code[sizeof(diagtramp_tmpl)];
	unsigned long long log;
	size_t i;
	int k;

	cave_addr = strtoull(cave, NULL, 16);
	log = cave_addr + 0x70;
	if (kopen() < 0)
		return (1);
	memcpy(code, diagtramp_tmpl, sizeof(code));
	/* fix the four imm32 log-slot addresses */
	k = 0;
	for (i = 0; i < sizeof(code); i++) {
		if (code[i] == 0x25 && i + 4 < sizeof(code) &&
		    (code[i - 1] == 0x3c || code[i - 1] == 0x34 ||
		     code[i - 1] == 0x04)) {
			patch_imm32(code, i + 1, log + 8 * (k++));
			i += 4;
		}
	}
	fprintf(stderr, "kv: diag trampoline (%d slots) log at %llx\n", k, log);
	if (kwrite(cave_addr, code, sizeof(code)) < 0)
		return (1);
	return (0);
}

static int
do_restore(void)
{
	unsigned long long entry;

	if (!patched) {
		fprintf(stderr, "kv: not patched\n");
		return (1);
	}
	entry = sysent_addr + slot_no * 24;
	if (kwrite(entry, orig_entry, sizeof(orig_entry)) < 0)
		return (1);
	fprintf(stderr, "kv: sysent[%llu] restored\n", slot_no);
	return (0);
}

static void
do_read_block(unsigned long long addr, size_t len)
{
	unsigned char buf[64];
	size_t i;

	if (len > sizeof(buf))
		len = sizeof(buf);
	if (kread(addr, buf, len) < 0)
		return;
	for (i = 0; i < len; i += 8) {
		fprintf(stderr, "  +%02zx: %016llx\n", i,
		    *(unsigned long long *)(void *)&buf[i]);
	}
}

/*
 * Direct syscall into KV_SLOT, bypassing libc's indirect syscall(0)
 * wrapper (whose extra context made _kmalloc misbehave).
 */
static long long
kv_rawcall(unsigned long long a1, unsigned long long a2,
    unsigned long long a3)
{
	unsigned long long ret, t1 = a1, t2 = a2, t3 = a3;
	unsigned long long slot = (unsigned long long)KV_SLOT;

	/*
	 * Direct syscall: rax=slot, rdi/rsi/rdx = args 1..3.
	 * rdi/rsi/rdx/rcx/r11 are all clobbered by the sequence and
	 * MUST be declared as such, or gcc keeps stale state in them
	 * across the asm (which trashed the restore logic afterwards).
	 */
	__asm __volatile(
	    "movq %1, %%rdi\n\t"
	    "movq %2, %%rsi\n\t"
	    "movq %3, %%rdx\n\t"
	    "movq %4, %%rax\n\t"
	    "syscall\n\t"
	    : "=a"(ret)
	    : "r"(t1), "r"(t2), "r"(t3), "r"(slot)
	    : "rcx", "r11", "rdi", "rsi", "rdx", "memory");
	return ((long long)ret);
}

static int
kv_call1(unsigned long long fn, unsigned long long a1,
    unsigned long long a2, unsigned long long a3)
{
	unsigned long long argblk[8];
	unsigned long long entry;
	unsigned char narg[4] = { 3, 0, 0, 0 };
	long long ret;

	argblk[0] = a1;
	argblk[1] = a2;
	argblk[2] = a3;

	static unsigned char orig_cave[128];
	static int cave_saved = 0;

	entry = sysent_addr + KV_SLOT * 24;
	if (kread(entry, orig_entry, sizeof(orig_entry)) < 0)
		return (-1);
	if (!cave_saved) {
		/* save the original sys_sbrk code we scribble over */
		if (kread(cave_addr, orig_cave, sizeof(orig_cave)) < 0)
			return (-1);
		cave_saved = 1;
	}
	if (kwrite(cave_addr, tramp, sizeof(tramp)) < 0 ||
	    kwrite(entry, narg, 4) < 0 ||
	    kwrite(entry + 8, &cave_addr, 8) < 0)
		return (-1);
	ret = kv_rawcall(fn, (unsigned long long)(uintptr_t)&argblk[0],
	    KV_MAGIC);
	kwrite(entry, orig_entry, sizeof(orig_entry));
	kwrite(cave_addr, orig_cave, sizeof(orig_cave));	/* put sbrk back */
	if (ret == -1) {
		fprintf(stderr, "kv: syscall failed: %s\n", strerror(errno));
		return (-1);
	}
	return (0);
}

static int
do_call(int argc, char **argv)
{
	unsigned long long fn, args[6];
	unsigned long long argblk[8];
	unsigned long long entry;
	unsigned char narg[4] = { 3, 0, 0, 0 };
	long long ret;
	int i, n;

	if (argc < 1)
		return (2);
	fn = strtoull(argv[0], NULL, 16);
	memset(args, 0, sizeof(args));
	n = argc - 1;
	if (n > 6)
		n = 6;
	for (i = 0; i < n; i++)
		args[i] = strtoull(argv[1 + i], NULL, 16);

	argblk[0] = fn;
	memcpy(&argblk[1], args, sizeof(args));

	/*
	 * Self-contained: patch sysent[KV_SLOT], issue the call, restore
	 * the original entry. The exposure window is a few instructions
	 * inside this single process, and the slot is an unassigned
	 * nosys number nothing else can invoke; the magic guard rejects
	 * any stray caller regardless.
	 */
	unsigned char code[sizeof(diagtramp_tmpl)];
	const unsigned char *tp;
	size_t sz, ii;
	int kk;
	unsigned long long log;

	if (kopen() < 0)
		return (1);
	entry = sysent_addr + KV_SLOT * 24;
	if (kread(entry, orig_entry, sizeof(orig_entry)) < 0)
		return (1);
	narg[0] = 3;
	if (getenv("KV_DIAG")) {
		memcpy(code, diagtramp_tmpl, sizeof(code));
		log = cave_addr + 0x70;
		kk = 0;
		for (ii = 0; ii < sizeof(code); ii++) {
			if (code[ii] == 0x25 && ii + 4 < sizeof(code) &&
			    (code[ii - 1] == 0x3c || code[ii - 1] == 0x34 ||
			     code[ii - 1] == 0x04)) {
				patch_imm32(code, ii + 1, log + 8 * (kk++));
				ii += 4;
			}
		}
		tp = code;
		sz = sizeof(code);
		fprintf(stderr, "kv: diag mode, %d log slots at %llx\n", kk, log);
	} else {
		tp = tramp;
		sz = sizeof(tramp);
	}
	if (kwrite(cave_addr, tp, sz) < 0 ||
	    kwrite(entry, narg, 4) < 0 ||
	    kwrite(entry + 8, &cave_addr, 8) < 0)
		return (1);

	ret = kv_rawcall(argblk[0], (unsigned long long)(uintptr_t)&argblk[1],
	    KV_MAGIC);

	kwrite(entry, orig_entry, sizeof(orig_entry));	/* restore */
	if (getenv("KV_DIAG")) {
		fprintf(stderr, "kv: LOG rdi/rsi/argp[0]/argp[1]/argp[2]:\n");
		do_read_block(cave_addr + 0x70, 40);
	}
	if (ret == -1) {
		fprintf(stderr, "kv: syscall failed: %s\n", strerror(errno));
		return (1);
	}
	printf("%llx\n", (unsigned long long)ret);
	return (0);
}

static int
do_read(const char *addr, const char *len)
{
	unsigned char buf[4096];
	unsigned long long a;
	long long l;
	size_t i;

	a = strtoull(addr, NULL, 16);
	l = strtoll(len, NULL, 0);
	if (l > (long long)sizeof(buf))
		l = sizeof(buf);
	if (kopen() < 0 || kread(a, buf, (size_t)l) < 0)
		return (1);
	for (i = 0; i < (size_t)l; i++) {
		printf("%02x", buf[i]);
		if ((i % 32) == 31 || i + 1 == (size_t)l)
			printf("\n");
	}
	return (0);
}

static int
do_write(const char *addr, const char *hex)
{
	unsigned char buf[4096];
	unsigned long long a;
	size_t n = 0;
	char b[3];

	a = strtoull(addr, NULL, 16);
	if (strlen(hex) % 2)
		return (2);
	while (hex[0] && hex[1] && n < sizeof(buf)) {
		b[0] = hex[0];
		b[1] = hex[1];
		b[2] = 0;
		buf[n++] = (unsigned char)strtoul(b, NULL, 16);
		hex += 2;
	}
	if (kopen() < 0 || kwrite(a, buf, n) < 0)
		return (1);
	fprintf(stderr, "kv: wrote %zu bytes at %llx\n", n, a);
	return (0);
}

/*
 * kv kread <hexaddr> <len> : read kernel memory via bcopy() through the
 * call trampoline (works for heap addresses /dev/kmem lseek cannot).
 * Set the bcopy address first: kv setbcopy <hexaddr>.
 */
static unsigned long long bcopy_addr;

static int
do_kread(const char *addr, const char *len)
{
	unsigned long long a;
	long long l;
	static unsigned char buf[4096];
	size_t i;

	a = strtoull(addr, NULL, 16);
	l = strtoll(len, NULL, 0);
	if (l > (long long)sizeof(buf))
		l = sizeof(buf);
	if (kopen() < 0)
		return (1);
	if (bcopy_addr == 0) {
		const char *e = getenv("KV_BCOPY");
		if (e == NULL) {
			fprintf(stderr, "kv: set KV_BCOPY=<bcopy addr>\n");
			return (1);
		}
		bcopy_addr = strtoull(e, NULL, 16);
	}
	if (kv_call1(bcopy_addr, a, (unsigned long long)(uintptr_t)buf,
	    (unsigned long long)l) != 0)
		return (1);
	for (i = 0; i < (size_t)l; i++) {
		printf("%02x", buf[i]);
		if ((i % 32) == 31 || i + 1 == (size_t)l)
			printf("\n");
	}
	return (0);
}

int
main(int argc, char **argv)
{
	if (argc < 2)
		goto usage;
	if (strcmp(argv[1], "setup") == 0) {
		if (argc != 5)
			goto usage;
		return (do_setup(argv[2], argv[3], argv[4]));
	}
	if (strcmp(argv[1], "restore") == 0)
		return (do_restore());
	if (strcmp(argv[1], "kread") == 0) {
		if (argc != 4)
			goto usage;
		return (do_kread(argv[2], argv[3]));
	}
	if (strcmp(argv[1], "setbcopy") == 0) {
		if (argc != 3)
			goto usage;
		bcopy_addr = strtoull(argv[2], NULL, 16);
		fprintf(stderr, "kv: bcopy = %llx\n", bcopy_addr);
		return (0);
	}
	if (strcmp(argv[1], "diag") == 0) {
		if (argc != 3)
			goto usage;
		return (do_diag(argv[2]));
	}
	if (strcmp(argv[1], "call") == 0)
		return (do_call(argc - 2, argv + 2));
	if (strcmp(argv[1], "read") == 0) {
		if (argc != 4)
			goto usage;
		return (do_read(argv[2], argv[3]));
	}
	if (strcmp(argv[1], "write") == 0) {
		if (argc != 4)
			goto usage;
		return (do_write(argv[2], argv[3]));
	}
usage:
	fprintf(stderr, "usage: kv setup <cave> <sysent> <slot>\n"
	    "       kv read <addr> <len>\n"
	    "       kv write <addr> <hex>\n"
	    "       kv call <fn> [a1..a6]\n"
	    "       kv restore\n");
	return (2);
}
