/*
 * csprng_force_zero.c — DF-0220 manufactured pre-reseed window (root, kmem).
 *
 * DF-0220 claims /dev/urandom & friends emit a deterministic keystream while
 * a per-CPU csprng_state is un-reseeded (reseed_cnt == 0).  On this kernel no
 * userspace reader can ever observe that condition: rand_initialize()
 * (kern_nrandom.c:485-560, SYSINIT SI_BOOT2_POST_SMP) pre-feeds every state's
 * pool[0] with ~15 KB (sizeof(struct globaldata) = 14976 B via
 * RAND_SRC_THREAD2) *before* any read_random can run, and csprng_get_random()
 * always attempts a reseed (ratecheck {0,0} first-call special case,
 * kern_time.c:1153-1157) *before* its emit loop (subr_csprng.c:134-149 vs
 * :151-166) — so the first successful reseed keys the cipher before the
 * first byte is emitted on every state.
 *
 * To characterize WHAT the window would emit (and prove the emit path is
 * real), this tool uses /dev/kmem (root) to manufacture the exact
 * precondition tuple on one idle per-CPU state:
 *
 *     reseed_cnt = 0, key = 0^32, cipher_ctx = 0^64, pool[0].bytes = 0,
 *     last_reseed = {0,0}
 *
 * after which an UNPRIVILEGED-style /dev/urandom read pinned to that CPU
 * returns:  block 1 = all-zero (chacha fixed point), following blocks =
 * deterministic chacha (sigma constants present, key re-derived from the
 * zero keystream).  The state self-heals within seconds (rand_thread_loop
 * refills pool[0]; next read reseeds).
 *
 * Struct offsets from kernel.debug DWARF (identical to the DF-5766 rig):
 *   key@0 (32 B), reseed_cnt@32, cipher_ctx@40 (64 B = input[16]),
 *   pool[0]@104 (pool.bytes is first member), pool stride 112,
 *   last_reseed@4256, sizeof(struct csprng_state) = 6656.
 *
 * Usage (root):
 *   csprng_force_zero <symaddr> info
 *   csprng_force_zero <symaddr> force  <cpu>
 *   csprng_force_zero <symaddr> check  <cpu>
 *   csprng_force_zero <symaddr> restore <cpu>   (from /tmp/stateK.bak)
 */
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdint.h>

#define OFF_KEY     0
#define OFF_RCNT    32
#define OFF_CTX     40
#define CTX_LEN     64
#define OFF_POOL    104
#define POOL_STRIDE 112
#define OFF_LAST    4256
#define STATE_SZ    6656

static int kd;

static int
rd(uint64_t addr, void *buf, size_t len)
{
	if (pread(kd, buf, len, (off_t)addr) != (ssize_t)len) {
		perror("pread kmem");
		return -1;
	}
	return 0;
}

static int
wr(uint64_t addr, const void *buf, size_t len)
{
	if (pwrite(kd, buf, len, (off_t)addr) != (ssize_t)len) {
		perror("pwrite kmem");
		return -1;
	}
	return 0;
}

static void
summary(uint64_t addr, int idx)
{
	uint8_t st[STATE_SZ];
	uint64_t rc, p0, tv[2];
	uint8_t key[32];
	int i, keynz = 0, ctxnz = 0;

	if (rd(addr, st, sizeof(st)) < 0)
		exit(1);
	memcpy(key, st + OFF_KEY, 32);
	memcpy(&rc, st + OFF_RCNT, 8);
	memcpy(&p0, st + OFF_POOL, 8);
	memcpy(tv, st + OFF_LAST, 16);
	for (i = 0; i < 32; i++)
		if (key[i])
			keynz = 1;
	for (i = 0; i < CTX_LEN; i++)
		if (st[OFF_CTX + i])
			ctxnz = 1;
	printf("state[%d] @%#llx: reseed_cnt=%llu pool0=%llu "
	    "key=%s ctx=%s last_reseed=%lld.%06lld\n",
	    idx, (unsigned long long)addr,
	    (unsigned long long)rc, (unsigned long long)p0,
	    keynz ? "NONZERO" : "ALL-ZERO",
	    ctxnz ? "NONZERO" : "ALL-ZERO",
	    (long long)tv[0], (long long)tv[1]);
}

int
main(int argc, char **argv)
{
	uint64_t sym, base;
	const char *act;
	int cpu, i, ncpu = 6;
	char bakpath[128];
	uint8_t zero[STATE_SZ];
	uint8_t bak[STATE_SZ];

	if (argc < 3) {
		fprintf(stderr, "usage: %s <csprng_pcpu-symaddr> "
		    "info|force|check|restore [cpu]\n", argv[0]);
		return 1;
	}
	sym = strtoull(argv[1], NULL, 0);
	act = argv[2];
	cpu = (argc > 3) ? atoi(argv[3]) : 2;

	kd = open("/dev/kmem", O_RDWR);
	if (kd < 0) {
		perror("open /dev/kmem (need root)");
		return 1;
	}
	if (rd(sym, &base, 8) < 0)
		return 1;
	printf("csprng_pcpu = %#llx\n", (unsigned long long)base);

	if (strcmp(act, "info") == 0) {
		for (i = 0; i < ncpu; i++)
			summary(base + (uint64_t)STATE_SZ * i, i);
		return 0;
	}
	snprintf(bakpath, sizeof(bakpath), "/tmp/state%d.bak", cpu);
	base += (uint64_t)STATE_SZ * cpu;

	if (strcmp(act, "force") == 0) {
		memset(zero, 0, sizeof(zero));
		if (rd(base, bak, STATE_SZ) < 0)
			return 1;
		{
			FILE *f = fopen(bakpath, "w");
			if (!f) { perror("backup"); return 1; }
			fwrite(bak, 1, STATE_SZ, f);
			fclose(f);
		}
		printf("backed up state[%d] (%d B) to %s\n", cpu, STATE_SZ, bakpath);
		/* key, cipher_ctx, reseed_cnt, pool[0].bytes, last_reseed -> 0 */
		if (wr(base + OFF_KEY, zero, 32) < 0) return 1;
		if (wr(base + OFF_RCNT, zero, 8) < 0) return 1;
		if (wr(base + OFF_CTX, zero, CTX_LEN) < 0) return 1;
		if (wr(base + OFF_POOL, zero, 8) < 0) return 1;
		if (wr(base + OFF_LAST, zero, 16) < 0) return 1;
		printf("FORCED state[%d] into pre-reseed condition "
		    "(reseed_cnt=0, key=0, ctx=0, pool0.bytes=0)\n", cpu);
		summary(base, cpu);
		return 0;
	}
	if (strcmp(act, "check") == 0) {
		summary(base, cpu);
		return 0;
	}
	if (strcmp(act, "restore") == 0) {
		FILE *f = fopen(bakpath, "r");
		if (!f) { perror(bakpath); return 1; }
		if (fread(bak, 1, STATE_SZ, f) != STATE_SZ) {
			fprintf(stderr, "short backup read\n");
			return 1;
		}
		fclose(f);
		if (wr(base, bak, STATE_SZ) < 0)
			return 1;
		printf("restored state[%d] from backup\n", cpu);
		summary(base, cpu);
		return 0;
	}
	if (strcmp(act, "peekctx") == 0) {
		/* Healthy keyed states must show the chacha sigma constants
		 * "expand 32-byte k" at input[0..3] == offset 40..55. */
		uint8_t b[64];
		int j;
		if (rd(base + OFF_CTX, b, 64) < 0)
			return 1;
		printf("state[%d] ctx@%d:", cpu, OFF_CTX);
		for (j = 0; j < 64; j++) {
			if (j % 16 == 0)
				printf("\n  %02d:", j);
			printf(" %02x", b[j]);
		}
		printf("\n  sigma@40: %.16s\n", (char *)b);
		return 0;
	}
	if (strcmp(act, "window") == 0) {
		/* In-process force + read: NO exec between force and read,
		 * so no exec-time consumer can burn the zero block first.
		 * fd is opened BEFORE the force.  Prints 4 x 32B blocks. */
		unsigned char blk[32];
		ssize_t r;
		int fd, k, j;

		if ((fd = open("/dev/urandom", O_RDONLY)) < 0) {
			perror("open urandom");
			return 1;
		}
		/* force (same writes as 'force') */
		{
			FILE *f;
			memset(zero, 0, sizeof(zero));
			if (rd(base, bak, STATE_SZ) < 0) return 1;
			snprintf(bakpath, sizeof(bakpath), "/tmp/state%d.bak", cpu);
			f = fopen(bakpath, "w");
			if (f) { fwrite(bak, 1, STATE_SZ, f); fclose(f); }
			if (wr(base + OFF_KEY, zero, 32) < 0) return 1;
			if (wr(base + OFF_RCNT, zero, 8) < 0) return 1;
			if (wr(base + OFF_CTX, zero, CTX_LEN) < 0) return 1;
			if (wr(base + OFF_POOL, zero, 8) < 0) return 1;
			if (wr(base + OFF_LAST, zero, 16) < 0) return 1;
		}
		printf("forced state[%d]; reading 4 x 32B in-process\n", cpu);
		for (k = 0; k < 4; k++) {
			r = read(fd, blk, 32);
			if (r != 32) { perror("read"); return 1; }
			printf("blk%d:", k + 1);
			for (j = 0; j < 32; j++)
				printf(" %02x", blk[j]);
			printf("\n");
		}
		close(fd);
		return 0;
	}
	if (strcmp(act, "window2") == 0) {
		/* Like window, but dumps the live cipher_ctx (sigma/key/ctr)
		 * after EACH read so the exact emit/rekey chain is visible. */
		unsigned char blk[32];
		uint8_t cx[64];
		ssize_t r;
		int fd, k, j;

		if ((fd = open("/dev/urandom", O_RDONLY)) < 0) {
			perror("open urandom");
			return 1;
		}
		memset(zero, 0, sizeof(zero));
		if (rd(base, bak, STATE_SZ) < 0) return 1;
		snprintf(bakpath, sizeof(bakpath), "/tmp/state%d.bak", cpu);
		{ FILE *f = fopen(bakpath, "w");
		  if (f) { fwrite(bak, 1, STATE_SZ, f); fclose(f); } }
		if (wr(base + OFF_KEY, zero, 32) < 0) return 1;
		if (wr(base + OFF_RCNT, zero, 8) < 0) return 1;
		if (wr(base + OFF_CTX, zero, CTX_LEN) < 0) return 1;
		if (wr(base + OFF_POOL, zero, 8) < 0) return 1;
		if (wr(base + OFF_LAST, zero, 16) < 0) return 1;
		printf("forced state[%d]; ctx after each 32B read:\n", cpu);
		for (k = 0; k < 4; k++) {
			r = read(fd, blk, 32);
			if (r != 32) { perror("read"); return 1; }
			if (rd(base + OFF_CTX, cx, 64) < 0) return 1;
			printf("blk%d:", k + 1);
			for (j = 0; j < 32; j++)
				printf(" %02x", blk[j]);
			printf("\n  ctx sigma=%.4s key0=", cx);
			for (j = 16; j < 32; j++)
				printf("%02x", cx[j]);
			/* counter = input[12..15] LE */
			{
				uint32_t c0, c1;
				memcpy(&c0, cx + 48, 4);
				memcpy(&c1, cx + 52, 4);
				printf(" ctr=%u(0x%x) ctr_hi=%u\n", c0, c0, c1);
			}
		}
		close(fd);
		return 0;
	}
	fprintf(stderr, "unknown action %s\n", act);
	return 1;
}
