/*
 * kmem_addrlen.c -- DF-2611 harness helper.
 *
 * Walks the kernel's ifnet_array (sys/net/if_var.h:736-740) through
 * /dev/kmem to find an interface by name, VALIDATES the computed
 * offsetof(struct ifnet, if_data) (+136) by printing ifi_type /
 * ifi_addrlen / ifi_mtu for every interface (vtnet0 must read
 * type=6/ETH, addrlen=6, mtu=1500; lo0 type=24/LOOP, addrlen=0,
 * mtu=16384), and optionally rewrites ifi_addrlen (offset 138).
 *
 * This simulates, on the QEMU guest, an interface whose link-layer
 * address length is not 6 (e.g. FireWire EUI-64, if_addrlen=8), which
 * the finding requires for the TLLA pad leak but which QEMU cannot
 * emulate with real hardware.
 *
 * usage: kmem_addrlen [ifname [new_addrlen]]
 *   no args  : dump all interfaces, validate offsets, change nothing
 *   1 arg    : dump + show target
 *   2 args   : dump + write new_addrlen into target's ifi_addrlen
 */
#include <sys/types.h>
#include <sys/elf_common.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h>
#include <errno.h>

#define IFDATA_OFF	136		/* offsetof(struct ifnet, if_data) */
#define ADDRLEN_OFF	(IFDATA_OFF + 2)	/* ifi_addrlen */
#define TYPE_OFF	(IFDATA_OFF + 0)	/* ifi_type */
#define MTU_OFF		(IFDATA_OFF + 8)	/* ifi_mtu (u_long) */

static int kmem = -1;

static int
kread(uint64_t addr, void *buf, size_t len)
{
	if (lseek(kmem, (off_t)addr, SEEK_SET) == (off_t)-1 ||
	    read(kmem, buf, len) != (ssize_t)len)
		return -1;
	return 0;
}

static int
kwrite(uint64_t addr, const void *buf, size_t len)
{
	if (lseek(kmem, (off_t)addr, SEEK_SET) == (off_t)-1 ||
	    write(kmem, buf, len) != (ssize_t)len)
		return -1;
	return 0;
}

/* find symbol address via nm from the booted kernel image */
static uint64_t
symaddr(const char *name)
{
	char cmd[256];
	FILE *p;
	char line[512], sym[256];
	uint64_t addr;
	int n;

	snprintf(cmd, sizeof(cmd), "nm -n /boot/kernel/kernel 2>/dev/null");
	p = popen(cmd, "r");
	if (!p) {
		perror("popen nm");
		return 0;
	}
	addr = 0;
	while (fgets(line, sizeof(line), p)) {
		n = sscanf(line, "%llx %*s %255s", &addr, sym);
		if (n == 2 && !strcmp(sym, name))
			break;
		addr = 0;
	}
	pclose(p);
	return addr;
}

static const char *
iftname(int t)
{
	switch (t) {
	case 6:  return "IFT_ETHER(6)";
	case 24: return "IFT_LOOP(24)";
	case 54: return "IFT_PROPVIRTUAL(54)";
	case 18: return "IFT_P2P_SERIAL? (18)";
	default: return "?";
	}
}

int
main(int argc, char **argv)
{
	uint64_t arr_p, ifp, ifnet_arr_addr;
	uint8_t buf[512];
	int count, i, found = 0, want_write = 0;
	uint8_t newlen = 0;
	const char *target = NULL;
	char name[17];

	if (argc > 2) {
		want_write = 1;
		newlen = (uint8_t)strtoul(argv[2], NULL, 0);
	}
	if (argc > 1)
		target = argv[1];

	kmem = open("/dev/kmem", O_RDWR);
	if (kmem < 0) {
		perror("open /dev/kmem");
		return 2;
	}
	ifnet_arr_addr = symaddr("ifnet_array");
	if (!ifnet_arr_addr) {
		fprintf(stderr, "cannot resolve ifnet_array symbol\n");
		return 2;
	}
	printf("ifnet_array symbol @ 0x%llx\n",
	    (unsigned long long)ifnet_arr_addr);
	if (kread(ifnet_arr_addr, &arr_p, sizeof(arr_p)) || !arr_p) {
		fprintf(stderr, "cannot read ifnet_array pointer\n");
		return 2;
	}
	if (kread(arr_p, buf, 8)) {
		fprintf(stderr, "cannot read ifnet_array {count,pad}\n");
		return 2;
	}
	count = *(int *)buf;
	printf("ifnet_array @ 0x%llx count=%d\n", (unsigned long long)arr_p,
	    count);
	if (count < 1 || count > 1024) {
		fprintf(stderr, "implausible count -- offsets wrong?\n");
		return 2;
	}

	for (i = 0; i < count; i++) {
		uint8_t *ent;
		uint64_t e;
		int ftype;
		uint8_t flen;
		unsigned long fmtu;

		if (kread(arr_p + 8 + (uint64_t)i * 8, &e, sizeof(e)) || !e)
			continue;
		if (kread(e, buf, sizeof(buf)))
			continue;
		ent = buf;
		memcpy(name, ent + 32, 16);	/* if_xname @ +32 */
		name[16] = 0;
		ftype = ent[TYPE_OFF];
		flen = ent[ADDRLEN_OFF];
		memcpy(&fmtu, ent + MTU_OFF, sizeof(fmtu));
		printf("ifnet[%d] 0x%llx %-8s type=%d(%s) addrlen=%u "
		    "mtu=%lu\n", i, (unsigned long long)e, name, ftype,
		    iftname(ftype), flen, fmtu);
		if (target && !strcmp(name, target)) {
			found = 1;
			if (want_write) {
				if (kwrite(e + ADDRLEN_OFF, &newlen, 1)) {
					fprintf(stderr, "kwrite failed: %s\n",
					    strerror(errno));
					return 2;
				}
				if (kread(e + ADDRLEN_OFF, &flen, 1))
					return 2;
				printf("WROTE %s if_addrlen=%u (readback)\n",
				    target, flen);
			}
		}
	}
	if (target && !found) {
		fprintf(stderr, "interface %s not found\n", target);
		return 1;
	}
	close(kmem);
	return 0;
}
