/* Harness: smbfs_vfsops.c caller-side strncpy misuse (DF-5710).
 *
 * Replicates VERBATIM the code from sys/vfs/smbfs/smbfs_vfsops.c:165-178
 * (smbfs_mount), with the real constants (MNAMELEN=80, struct statfs field
 * embedded in a mount-like struct) and the real kernel strncpy compiled
 * unmodified from /usr/src/sys/libkern/strncpy.c.
 *
 * index() == strchr() (both BSD legacy); mapped to strchr for userland cc.
 *
 * Guard-page rig: the mount-struct analogue is placed at the end of a
 * readable page followed by a PROT_NONE guard page (kernel-heap analogue),
 * or given a large runway to visualize the unbounded zero-fill.
 *
 * Cases (each in a forked child so SIGSEGV is observable, not fatal):
 *   0 benign : user="joe" srv="fs01" share="pub"        -> must exit 0
 *   1 hostile-A: user = 76*'u' (fills budget1)           -> 2nd strncpy
 *               budget = pe-pc-2 == -1 -> SIZE_MAX copy -> SIGSEGV
 *   2 hostile-B: user = 75*'u' -> '@' at pe-2, '/' path  -> 3rd strncpy
 *               (share name) budget == -1 -> SIZE_MAX copy -> SIGSEGV
 *   3 runway : user = 76*'u' with 1 MB runway: shows the copy/pad runs
 *               unbounded (zeroes everything past f_mntfromname) and
 *               still dies at the far guard page.
 *   4 trace  : prints the exact budget arithmetic for both hostile routes.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>

char *kstrncpy(char *dst, const char *src, size_t n); /* kernel strncpy */

#define MNAMELEN 80

struct mount_shim {
	char    heap_before[64];                      /* analogue: heap before mnt_stat */
	struct { char f_mntfromname[MNAMELEN]; } mnt_stat;
	char    heap_after[64];                       /* analogue: heap after mnt_stat  */
};

static const char *tag;

static void
run_mount(const char *vc_username, const char *vc_srvname, const char *ss_name,
    size_t runway, int verbose)
{
	char *base, *pc, *pe;
	size_t pagesz = (size_t)sysconf(_SC_PAGESIZE);
	size_t regionsz = runway + 2 * pagesz;

	base = mmap(NULL, regionsz, PROT_READ | PROT_WRITE,
	    MAP_PRIVATE | MAP_ANON, -1, 0);
	if (base == MAP_FAILED) { perror("mmap"); _exit(90); }
	/* guard page at the end of the region */
	if (mprotect(base + regionsz - pagesz, pagesz, PROT_NONE) != 0) {
		perror("mprotect"); _exit(91);
	}
	/* place the mount analogue just before the guard page,
	 * or at the start if we were given a runway */
	{
		char *p = base + (runway ? 0 : (regionsz - pagesz - sizeof(struct mount_shim)));
		struct mount_shim *mp = (struct mount_shim *)p;
		long long b1, b2, b3;

		memset(mp, 0xA5, sizeof(*mp));
		pc = mp->mnt_stat.f_mntfromname;
		pe = pc + sizeof(mp->mnt_stat.f_mntfromname);
		bzero(pc, MNAMELEN);
		*pc++ = '/';
		*pc++ = '/';
		b1 = (long long)(pe - pc - 2);
		if (verbose)
			printf("[%s] budget1=%lld user='%s'\n", tag, b1, vc_username);
		if (pe - pc > 2) {                                  /* FIXED guard */
			pc = strchr(kstrncpy(pc, vc_username, (size_t)b1), 0);
			b2 = (long long)(pe - pc - 2);
			if (verbose)
				printf("[%s] after user: pc offset=%td budget2=%lld\n",
				    tag, pc - (char *)mp, b2);
			if (pe - pc > 2) {                          /* FIXED guard */
				*(pc++) = '@';
				b2 = (long long)(pe - pc - 2);
				if (verbose)
					printf("[%s] after user+'@': pc offset=%td budget2=%lld\n",
					    tag, pc - (char *)mp, b2);
				pc = strchr(kstrncpy(pc, vc_srvname, (size_t)b2), 0);
				b3 = (long long)(pe - pc - 2);
				if (verbose)
					printf("[%s] after srv: pc offset=%td budget3=%lld\n",
					    tag, pc - (char *)mp, b3);
				if (pe - pc > 2) {                  /* FIXED guard */
					*(pc++) = '/';
					b3 = (long long)(pe - pc - 2);
					if (verbose)
						printf("[%s] after srv+'/': pc offset=%td budget3=%lld "
						    "(share='%s')\n", tag, pc - (char *)mp, b3, ss_name);
					kstrncpy(pc, ss_name, (size_t)b3);
				}
			}
		}
		if (verbose)
			printf("[%s] survived; f_mntfromname='%.79s'\n", tag,
			    mp->mnt_stat.f_mntfromname);
	}
	_exit(0);
}

static int
child_case(void (*fn)(const char *, const char *, const char *, size_t, int),
    const char *u, const char *s, const char *h, size_t runway, int verbose,
    char *desc)
{
	pid_t pid = fork();
	int st;

	if (pid == 0) {
		fn(u, s, h, runway, verbose);
		_exit(0); /* not reached */
	}
	waitpid(pid, &st, 0);
	if (WIFSIGNALED(st) && WTERMSIG(st) == SIGSEGV) {
		printf("  -> result: SIGSEGV (write crossed guard page)  [%s]\n", desc);
		return 1;
	} else if (WIFEXITED(st)) {
		printf("  -> result: clean exit(%d)                       [%s]\n",
		    WEXITSTATUS(st), desc);
		return 0;
	}
	printf("  -> result: other status %d                       [%s]\n", st, desc);
	return -1;
}

int
main(void)
{
	char u76[77], u75[76];

	memset(u76, 'u', sizeof(u76)); u76[76] = 0;
	memset(u75, 'u', sizeof(u75)); u75[75] = 0;

	setvbuf(stdout, NULL, _IONBF, 0);

	printf("== FIXED caller (pe - pc > 2 guards): hostile cases must now survive ==\n");
	printf("MNAMELEN=%d sizeof(f_mntfromname)=%zu budget1(78-2)=%lld\n\n",
	    MNAMELEN, sizeof(((struct mount_shim *)0)->mnt_stat.f_mntfromname),
	    (long long)(MNAMELEN - 4));

	tag = "case0 benign";
	printf("case0 benign user='joe' srv='fs01' share='pub' (no guard, adjacent heap intact)\n");
	child_case(run_mount, "joe", "fs01", "pub", 0, 1, "baseline");

	tag = "case1 user-route";
	printf("\ncase1 user=76*'u' -> budget2==-1 -> strncpy(dst,src,SIZE_MAX)\n");
	child_case(run_mount, u76, "SRV", "pub", 0, 1, "user-route guard");

	tag = "case2 share-route";
	printf("\ncase2 user=75*'u' -> budget3==-1 on the SHARE-name strncpy\n");
	child_case(run_mount, u75, "SRV", "pub", 0, 1, "share-route guard");

	tag = "case3 runway";
	printf("\ncase3 user=76*'u' with 1MB runway: unbounded pad visualization\n");
	child_case(run_mount, u76, "SRV", "pub", 1024 * 1024, 0, "runway");
	printf("  (child zeroed everything from f_mntfromname+79 to the 1MB guard:\n"
	       "   kernel analogue = whole struct mount and adjacent heap zeroed)\n");

	printf("\nVERDICT (fixed): every case clean exit + NUL-terminated result == fix effective\n");
	return 0;
}
