/*
 * probe.c -- DF-5015 verification: read syscons history state via /dev/kmem.
 * (root-only evidence tool; the bug itself is triggered by unpriv ioctls)
 *
 * usage: probe state          print vtb size/buffer/tail of console scp
 *        probe cellsum        count Z-cells (0x075a) in [buf-4096, buf)
 *                             and in the buffer itself
 *        probe snap <file>    hexdump [buf-4096, buf+4096) to file
 */
#include <fcntl.h>
#include <kvm.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define MAIN_SOFTC      0xffffffff81b25700ULL
#define OFF_CONSOLE_SCP 112
#define OFF_HISTORY     400
#define OFF_VTB_SIZE    16
#define OFF_VTB_BUFFER  24
#define OFF_VTB_TAIL    32

static kvm_t *kd;

static int
kread(uint64_t kva, void *buf, size_t len)
{
	return kvm_read(kd, (unsigned long)kva, buf, len) == (ssize_t)len
	    ? 0 : -1;
}

static uint64_t
kread64(uint64_t kva)
{
	uint64_t v = 0;

	if (kread(kva, &v, sizeof(v)))
		return 0;
	return v;
}

int
main(int argc, char **argv)
{
	uint64_t scp, vtb, buf;
	int size, tail;

	if (argc < 2)
		return 64;
	kd = kvm_open(NULL, "/dev/kmem", NULL, O_RDONLY, "probe");
	if (kd == NULL) {
		fprintf(stderr, "kvm_open failed\n");
		return 1;
	}
	scp = kread64(MAIN_SOFTC + OFF_CONSOLE_SCP);
	vtb = kread64(scp + OFF_HISTORY);
	if (kread(vtb + OFF_VTB_SIZE, &size, 4))
		return 2;
	buf = kread64(vtb + OFF_VTB_BUFFER);
	if (kread(vtb + OFF_VTB_TAIL, &tail, 4))
		return 2;

	if (!strcmp(argv[1], "state")) {
		printf("scp=%#llx vtb=%#llx size=%d buf=%#llx tail=%d\n",
		    (unsigned long long)scp, (unsigned long long)vtb, size,
		    (unsigned long long)buf, tail);
		return 0;
	}
	if (!strcmp(argv[1], "cellsum")) {
		uint16_t cells[2048];
		int i, before = 0, inbuf = 0;

		memset(cells, 0, sizeof(cells));
		if (kread(buf - 4096, cells, 4096) == 0) {
			for (i = 0; i < 2048; i++)
				if (cells[i] == 0x075a)
					before++;
		} else {
			printf("note: page before buf unreadable\n");
		}
		memset(cells, 0, sizeof(cells));
		if (kread(buf, cells, size * 2) == 0) {
			for (i = 0; i < size && i < 2048; i++)
				if (cells[i] == 0x075a)
					inbuf++;
		}
		printf("zcells_before=%d zcells_inbuf=%d\n", before, inbuf);
		return 0;
	}
	if (!strcmp(argv[1], "snap") && argc == 3) {
		FILE *f = fopen(argv[2], "w");
		unsigned char raw[8192];
		int i;

		if (f == NULL)
			return 2;
		memset(raw, 0, sizeof(raw));
		if (kread(buf - 4096, raw, 8192) != 0)
			fprintf(f, "# partial read\n");
		for (i = 0; i < 8192; i += 16) {
			fprintf(f, "%+#10llx %04x %04x %04x %04x %04x %04x "
			    "%04x %04x\n",
			    (unsigned long long)(buf - 4096 + i),
			    *(uint16_t *)(raw + i), *(uint16_t *)(raw + i + 2),
			    *(uint16_t *)(raw + i + 4), *(uint16_t *)(raw + i + 6),
			    *(uint16_t *)(raw + i + 8), *(uint16_t *)(raw + i + 10),
			    *(uint16_t *)(raw + i + 12), *(uint16_t *)(raw + i + 14));
		}
		fclose(f);
		printf("snap written (buf=%#llx)\n", (unsigned long long)buf);
		return 0;
	}
	return 64;
}
