⬢ DragonFlyBSD Kernel Audit
DF-3092 / tailcheck.c
← back to finding ↓ download raw
/* DF-3092 offline image verifier.
 *
 * Checks the crafted crafttail.c image (fixed geometry: 512B sectors,
 * SecPerClust=1, Res=1, FATs=1, FATsecs=17, RootDirEnts=16, Huge=4127
 * => first data sector 19, pm_maxcluster=4109, FAT entry capacity 4352):
 *
 *   C1: every FAT entry for a cluster > pm_maxcluster (4110..4351) is
 *       still 0 (free)  -- i.e. NO phantom cluster was ever allocated,
 *       chained, or linked by any operation.
 *   C2: no root-dir entry has a start cluster > pm_maxcluster.
 *   C3: BIG.TXT chain walk stays within 2..pm_maxcluster and terminates
 *       at an EOF marker without cycling; optional expected file size
 *       check (argv[2]).
 *
 * build: cc -O -o tailcheck tailcheck.c
 * usage: ./tailcheck image.img [expected_BIG_TXT_size]
 * exit: 0 = all pass, 1 = a check FAILED (prints which)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#define SECSZ 512
#define FATSECS 17
#define FIRSTCLUST_BLK 19
#define HUGE_SECS 4127
#define MAXCLUST 4109		/* pm_maxcluster */
#define FATENTS (FATSECS * 256)/* 4352 FAT16 entries fit in the FAT */

static uint8_t img[HUGE_SECS * SECSZ + 8 * SECSZ];
static size_t imgsiz;

static uint16_t
rd16(const uint8_t *p)
{
	return ((uint16_t)p[0] | ((uint16_t)p[1] << 8));
}

static uint32_t
rd32(const uint8_t *p)
{
	return ((uint32_t)rd16(p) | ((uint32_t)rd16(p + 2) << 16));
}

int
main(int argc, char **argv)
{
	FILE *f;
	const uint8_t *fat, *root;
	uint16_t e;
	unsigned long n, c, steps;
	int fails = 0;

	if (argc < 2) {
		fprintf(stderr, "usage: %s image.img [expected_size]\n",
		    argv[0]);
		return (1);
	}
	f = fopen(argv[1], "rb");
	if (!f) { perror("fopen"); return (1); }
	imgsiz = fread(img, 1, sizeof(img), f);
	fclose(f);
	if (imgsiz < HUGE_SECS * SECSZ) {
		fprintf(stderr, "short image %zu\n", imgsiz);
		return (1);
	}

	/* sanity: BPB matches crafttail geometry */
	if (img[13] != 1 || rd16(img + 14) != 1 || img[16] != 1 ||
	    rd16(img + 22) != FATSECS || rd32(img + 32) != HUGE_SECS) {
		fprintf(stderr, "image is not the crafttail geometry\n");
		return (1);
	}

	fat = img + SECSZ;
	root = img + (FIRSTCLUST_BLK - 1) * SECSZ;

	/* C1: FAT entries beyond pm_maxcluster must all be free */
	for (n = MAXCLUST + 1; n < FATENTS; n++) {
		e = rd16(fat + n * 2);
		if ((e & 0xffff) != 0) {
			printf("C1 FAIL: FAT[%lu]=0x%04x nonzero "
			    "(phantom cluster was touched!)\n", n, e);
			fails++;
		}
	}
	if (!fails)
		printf("C1 pass: FAT entries %u..%u all zero "
		    "(no phantom allocation ever reached the FAT)\n",
		    MAXCLUST + 1, FATENTS - 1);

	/* C2: no dir entry starts beyond pm_maxcluster */
	for (n = 0; n < 16; n++) {
		const uint8_t *de = root + n * 32;
		uint16_t s;
		if (de[0] == 0x00 || de[0] == 0xE5)
			continue;
		s = rd16(de + 26);
		if (s > MAXCLUST) {
			printf("C2 FAIL: dir entry %lu '%.11s' start=%u > "
			    "maxcluster\n", n, (const char *)de, s);
			fails++;
		}
	}
	if (!fails)
		printf("C2 pass: all dir entries start within 2..%u\n",
		    MAXCLUST);

	/* C3: BIG.TXT chain bounded + terminates at EOF */
	for (n = 0; n < 16; n++) {
		const uint8_t *de = root + n * 32;
		if (de[0] == 0x00 || de[0] == 0xE5)
			continue;
		if (memcmp(de, "BIG     TXT", 11) != 0)
			continue;
		c = rd16(de + 26);
		steps = 0;
		while (c >= 2 && c <= MAXCLUST) {
			e = rd16(fat + c * 2);
			if (e >= 0xFFF8)
				break;
			if (e < 2 || e > MAXCLUST) {
				printf("C3 FAIL: FAT[%lu]=0x%04x out of "
				    "range\n", c, e);
				fails++;
				break;
			}
			c = e;
			if (++steps > MAXCLUST) {
				printf("C3 FAIL: BIG.TXT chain cycles\n");
				fails++;
				break;
			}
		}
		printf("C3 %s: BIG.TXT chain ends at cluster %lu "
		    "(EOF), dir size=%lu\n",
		    (c <= MAXCLUST) ? "pass" : "FAIL", c,
		    (unsigned long)rd32(de + 28));
		if (c > MAXCLUST)
			fails++;
		if (argc > 2) {
			unsigned long want = strtoul(argv[2], NULL, 0);
			unsigned long got = rd32(de + 28);
			if (got != want) {
				printf("C3 FAIL: BIG.TXT size %lu != "
				    "expected %lu\n", got, want);
				fails++;
			}
		}
		break;
	}

	printf("%s\n", fails ? "TAILCHECK: FAILED" : "TAILCHECK: ALL PASS");
	return (fails ? 1 : 0);
}