⬢ DragonFlyBSD Kernel Audit
DF-3223 / patch_inode.c
← back to finding ↓ download raw
/*
 * DF-3223 patcher: offline-patch a HAMMER1 image so a *live* PFS#0 inode
 * record (rec_type=HAMMER_RECTYPE_INODE, delete_tid==0, obj_id>1) carries
 * data_offset == 0 in its B-Tree leaf element.
 *
 * hammer_btree_extract() (sys/vfs/hammer/hammer_btree.c:728-731) returns
 * SUCCESS with cursor->data == NULL when data_offset == 0 (the early
 * return happens BEFORE the CRC test), so prune_check_nlinks()
 * (sys/vfs/hammer/hammer_prune.c:330-332) dereferences
 * cursor->data->inode.nlinks == NULL+72 -> kernel-mode page fault.
 *
 * Scans the image at 512-byte granularity for valid B-Tree leaf nodes
 * (self-identified by their stored node CRC), rewrites leaf->data_offset
 * and recomputes the node CRC (auto-detects vol_version<=6 crc32 vs >=7
 * iscsi_crc32 by checking which algorithm reproduces the stored CRC).
 *
 * usage: ./patch_inode <image>          (sets data_offset = 0)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>

#define	BUFSIZE		4096		/* hammer btree node size */
#define	BTREE_TYPE_L	(('L') & 0xff)
#define	RECTYPE_INODE	0x0001
#define	LOCALIZE_PSEUDOFS_MASK	0xFFFF0000U

/* leaf element offsets (see hammer_btree.h) */
#define	E_OFF_OBJID	0
#define	E_OFF_CRTID	16
#define	E_OFF_DELTID	24
#define	E_OFF_RTYPES	32		/* 2 bytes */
#define	E_OFF_LOCAL	36		/* 4 bytes */
#define	E_OFF_DATAOFF	48		/* 8 bytes */
#define	E_OFF_DATALEN	56		/* 4 bytes */

/* ------------------------------------------------------------------ */
/* CRC-32 (poly 0xEDB88320, init/xorout 0xFFFFFFFF)  - vol version <=6 */
static uint32_t crc32_tab[256];
static void
crc32_init(void)
{
	for (uint32_t i = 0; i < 256; i++) {
		uint32_t c = i;
		for (int k = 0; k < 8; k++)
			c = (c & 1) ? 0xEDB88320U ^ (c >> 1) : c >> 1;
		crc32_tab[i] = c;
	}
}
static uint32_t
crc32_buf(const void *p, size_t n)
{
	const unsigned char *b = p;
	uint32_t c = 0xFFFFFFFFU;
	for (size_t i = 0; i < n; i++)
		c = crc32_tab[(c ^ b[i]) & 0xff] ^ (c >> 8);
	return (c ^ 0xFFFFFFFFU);
}

/* CRC-32C Castagnoli (poly 0x82F63B78)             - vol version >=7 */
static uint32_t crc32c_tab[256];
static void
crc32c_init(void)
{
	for (uint32_t i = 0; i < 256; i++) {
		uint32_t c = i;
		for (int k = 0; k < 8; k++)
			c = (c & 1) ? 0x82F63B78U ^ (c >> 1) : c >> 1;
		crc32c_tab[i] = c;
	}
}
static uint32_t
crc32c_buf(const void *p, size_t n)
{
	const unsigned char *b = p;
	uint32_t c = 0xFFFFFFFFU;
	for (size_t i = 0; i < n; i++)
		c = crc32c_tab[(c ^ b[i]) & 0xff] ^ (c >> 8);
	return (c ^ 0xFFFFFFFFU);
}

int
main(int argc, char **argv)
{
	unsigned char node[BUFSIZE];
	uint32_t stored, calc_a, calc_b, newcrc;
	off_t off;
	int fd, count, patched = 0, nodes = 0;

	if (argc < 2) {
		fprintf(stderr, "usage: %s <image>\n", argv[0]);
		exit(2);
	}

	crc32_init();
	crc32c_init();

	fd = open(argv[1], O_RDWR);
	if (fd < 0) { perror("open"); exit(2); }

	for (off = 0; ; off += 512) {
		if (lseek(fd, off, SEEK_SET) < 0) break;
		if (read(fd, node, BUFSIZE) != BUFSIZE) break;

		/* node header: crc(0) r01(4) parent(8) count(16) type(20) */
		memcpy(&stored, node, 4);
		memcpy(&count, node + 16, 4);
		if (node[20] != BTREE_TYPE_L)
			continue;
		if (count < 1 || count > 63)
			continue;
		/* CRC over node minus first 4 bytes */
		calc_a = crc32_buf(node + 4, BUFSIZE - 4);
		calc_b = crc32c_buf(node + 4, BUFSIZE - 4);
		if (stored != calc_a && stored != calc_b)
			continue;	/* not a valid node at this offset */
		nodes++;
		{
			int is_c = (stored == calc_b);
			int hit = 0;
			for (int i = 0; i < count; i++) {
				unsigned char *elm = node + 64 + 64 * i;
				uint16_t rt;
				uint32_t loc;
				uint64_t objid, deltid, doff;
				int32_t dl;
				memcpy(&rt, elm + E_OFF_RTYPES, 2);
				memcpy(&loc, elm + E_OFF_LOCAL, 4);
				memcpy(&objid, elm + E_OFF_OBJID, 8);
				memcpy(&deltid, elm + E_OFF_DELTID, 8);
				memcpy(&doff, elm + E_OFF_DATAOFF, 8);
				memcpy(&dl, elm + E_OFF_DATALEN, 4);
				if (rt != RECTYPE_INODE)
					continue;
				if (loc & LOCALIZE_PSEUDOFS_MASK)
					continue;	/* not PFS#0 */
				if (deltid != 0)
					continue;	/* not a live inode */
				if ((int64_t)objid <= 1)
					continue;	/* skip root inode */
				printf("found live inode leaf @off=%jd: "
				       "objid=%016jx loc=%08x "
				       "data_off=%016jx data_len=%d -> 0\n",
				       (intmax_t)off, (uintmax_t)objid,
				       loc, (uintmax_t)doff, dl);
				doff = 0;
				memcpy(elm + E_OFF_DATAOFF, &doff, 8);
				hit = 1;
				patched++;
			}
			if (hit) {
				newcrc = is_c ? crc32c_buf(node + 4, BUFSIZE - 4)
					      : crc32_buf(node + 4, BUFSIZE - 4);
				memcpy(node, &newcrc, 4);
				if (lseek(fd, off, SEEK_SET) < 0 ||
				    write(fd, node, BUFSIZE) != BUFSIZE) {
					perror("write"); exit(1);
				}
				printf("patched node @off=%jd (crc algo %s)\n",
				       (intmax_t)off, is_c ? "iscsi/v7" : "crc32/v6");
			}
		}
	}
	printf("done: %d valid leaf nodes scanned, %d live inode records "
	       "patched to data_offset=0\n", nodes, patched);
	close(fd);
	return (patched == 0);
}