DragonFlyBSD Kernel Audit
DF-2675 / bufscan.c
← back to finding ↓ download raw
/*
 * DF-2675 bufscan: locate struct buf's b_kvabase field by signature
 * (buf[n].b_kvabase - buf[n-1].b_kvabase == MAXBSIZE), then report the
 * buffer-slot KVA range and, for each header, the number of present PTEs
 * in its slot (via /dev/mem page-table walk restricted to that range).
 *
 * Output: "SLOTBASE <kva>" then "OVR n=<header> kva=<slot kva> pages=<p>"
 * for every slot with more than MAXBSIZE/PAGE_SIZE (16) present PTEs.
 */
#include <sys/types.h>
#include <err.h>
#include <fcntl.h>
#include <kvm.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define PMASK 0x000FFFFFFFFFF000ULL
#define MAXBSIZE_ 65536

static int memfd;
static uint64_t KPML4;

static int
rdphys(uint64_t pa, void *buf, size_t len)
{
	return (pread(memfd, buf, len, (off_t)pa) == (ssize_t)len);
}

static uint64_t
virt2pte(uint64_t va, uint64_t *pte_out)
{
	uint64_t pml4[512], pud[512], pd[512];
	int i4, i3, i2;

	i4 = (int)((va >> 39) & 511); i3 = (int)((va >> 30) & 511); i2 = (int)((va >> 21) & 511);
	if (!rdphys(KPML4, pml4, 4096)) return 1;
	if (!(pml4[i4] & 1)) return 1;
	if (!rdphys(pml4[i4] & PMASK, pud, 4096)) return 1;
	if (!(pud[i3] & 1)) return 1;
	if (pud[i3] & 0x80) return 2;	/* 1GB */
	if (!rdphys(pud[i3] & PMASK, pd, 4096)) return 1;
	if (!(pd[i2] & 1)) return 1;
	if (pd[i2] & 0x80) return 2;	/* 2MB */
	{
		uint64_t pt[512];
		if (!rdphys(pd[i2] & PMASK, pt, 4096)) return 1;
		*pte_out = pt[(va >> 12) & 511];
	}
	return 0;
}

int
main(void)
{
	kvm_t *kd;
	struct nlist nl[] = { { "_buf" }, { "_KPML4phys" }, { NULL } };
	unsigned char *arr;
	long nbuf, szbuf, n;
	int off, bestoff = -1;
	long besthits = 0;

	kd = kvm_open(NULL, NULL, NULL, O_RDONLY, "bufscan");
	if (!kd) return 1;
	if (kvm_nlist(kd, nl) != 0 || nl[0].n_value == 0 || nl[1].n_value == 0)
		errx(1, "nlist buf/KPML4phys");
	if (kvm_read(kd, nl[1].n_value, &KPML4, 8) != 8)
		errx(1, "read KPML4phys");

	{
		size_t l;
		int v;
		l = sizeof(v);
		if (sysctlbyname("vfs.nbuf", &v, &l, NULL, 0) < 0)
			err(1, "sysctl vfs.nbuf");
		nbuf = v;
		l = sizeof(v);
		if (sysctlbyname("debug.sizeof.buf", &v, &l, NULL, 0) < 0)
			err(1, "sysctl debug.sizeof.buf");
		szbuf = v;
	}
	kvm_close(kd);
	fprintf(stderr, "nbuf=%ld sizeof(buf)=%ld\n", nbuf, szbuf);

	memfd = open("/dev/mem", O_RDONLY);
	if (memfd < 0) err(1, "/dev/mem");

	arr = malloc(nbuf * szbuf);
	if (!arr) err(1, "malloc");
	kd = kvm_open(NULL, NULL, NULL, O_RDONLY, "bufscan2");
	if (!kd) return 1;
	{
		uint64_t bufva;
		if (kvm_read(kd, nl[0].n_value, &bufva, 8) != 8)
			errx(1, "kvm_read &buf pointer");
		fprintf(stderr, "buf array VA = %#llx\n", (unsigned long long)bufva);
		if (kvm_read(kd, bufva, arr, nbuf * szbuf) != nbuf * szbuf)
			errx(1, "kvm_read buf array (%ld bytes)", nbuf * szbuf);
	}
	kvm_close(kd);

	for (off = 0; off + 8 <= szbuf; off += 8) {
		long hits = 0;
		uint64_t prev = 0;
		for (n = 0; n < nbuf; n++) {
			uint64_t v;
			memcpy(&v, arr + n * szbuf + off, 8);
			if (prev && v && v - prev == MAXBSIZE_)
				++hits;
			if (v)
				prev = v;
			if (hits > besthits) { besthits = hits; bestoff = off; }
		}
	}
	fprintf(stderr, "b_kvabase offset = %d (hits=%ld of %ld)\n", bestoff, besthits, nbuf);
	if (bestoff < 0 || besthits < nbuf / 2)
		errx(1, "could not locate b_kvabase field");

	{
		uint64_t base0, last = 0;
		memcpy(&base0, arr + bestoff, 8);
		for (n = nbuf - 1; n >= 0; n--) {
			memcpy(&last, arr + n * szbuf + bestoff, 8);
			if (last) break;
		}
		printf("SLOTBASE %#llx slots %ld .. %#llx\n",
		       (unsigned long long)base0, nbuf,
		       (unsigned long long)last);

		/* count present PTEs per slot; report slots > 16 */
		for (n = 0; n < nbuf; n++) {
			uint64_t kv, p;
			int pages = 0, i, rc;
			memcpy(&kv, arr + n * szbuf + bestoff, 8);
			if (kv == 0) continue;
			for (i = 0; i < MAXBSIZE_ / 4096 + 2; i++) {
				rc = virt2pte(kv + (uint64_t)i * 4096, &p);
				if (rc != 0) {
					if (rc == 2) { pages = -1; }
					break;
				}
				if (p & 1) pages++;
			}
			if (pages > MAXBSIZE_ / 4096)
				printf("OVR n=%ld kva=%#llx pages=%d\n", n,
				       (unsigned long long)kv, pages);
		}
	}
	return 0;
}