⬢ DragonFlyBSD Kernel Audit
DF-3200 / fix.diff
← back to finding ↓ download raw
--- a/sys/sys/devfs_rules.h	2026-09-08 10:19:34.389993596 +0000
+++ b/sys/sys/devfs_rules.h	2026-09-08 10:20:01.509645297 +0000
@@ -50,13 +50,13 @@
 	u_long	rule_cmd;
 
 	char	*mntpoint;
-	u_char	mntpointlen;
+	u_int16_t mntpointlen;
 
 	char	*name;
-	u_char	namlen;
+	u_int16_t namlen;
 
 	char	*linkname;
-	u_char	linknamlen;
+	u_int16_t linknamlen;
 
 
 	u_long	dev_type;	/* Type of device to which the rule applies */
--- a/sys/vfs/devfs/devfs_rules.c	2026-09-08 10:19:34.389993596 +0000
+++ b/sys/vfs/devfs/devfs_rules.c	2026-09-08 10:20:57.076931616 +0000
@@ -40,6 +40,7 @@
 #include <sys/fcntl.h>
 #include <sys/device.h>
 #include <sys/mount.h>
+#include <sys/jail.h>
 #include <sys/devfs.h>
 #include <sys/devfs_rules.h>
 
@@ -57,6 +58,8 @@
 static void devfs_rule_create_link(struct devfs_node *, struct devfs_rule *);
 static int devfs_rule_checkname(struct devfs_rule *, struct devfs_node *);
 
+typedef TAILQ_HEAD(devfs_node_list, devfs_node) devfs_node_list_t;
+
 static struct objcache	*devfs_rule_cache;
 static struct lock	devfs_rule_lock;
 
@@ -79,7 +82,7 @@
 devfs_rule_alloc(struct devfs_rule_ioctl *templ)
 {
 	struct devfs_rule *rule;
-	size_t len;
+	size_t len, i, stars;
 
 	rule = objcache_get(devfs_rule_cache, M_WAITOK);
 	memset(rule, 0, sizeof(struct devfs_rule));
@@ -89,7 +92,7 @@
 		/* NOTREACHED */
 
 	len = strlen(templ->mntpoint);
-	if (len == 0)
+	if (len == 0 || len >= sizeof(templ->mntpoint))
 		goto error_out;
 		/* NOTREACHED */
 
@@ -102,7 +105,22 @@
 			/* NOTREACHED */
 
 		len = strlen(templ->name);
-		if (len == 0)
+		if (len == 0 || len >= sizeof(templ->name))
+			goto error_out;
+			/* NOTREACHED */
+
+		/*
+		 * DF-3202: wildCaseCmp() recurses once per '*' in the pattern
+		 * (80 byte frames); a few hundred stars exhaust the kernel
+		 * stack during rule application.  Real rules never come close
+		 * to this many wildcards.
+		 */
+		stars = 0;
+		for (i = 0; i < len; ++i) {
+			if (templ->name[i] == '*')
+				++stars;
+		}
+		if (stars > 32)
 			goto error_out;
 			/* NOTREACHED */
 
@@ -116,7 +134,7 @@
 			/* NOTREACHED */
 
 		len = strlen(templ->linkname);
-		if (len == 0)
+		if (len == 0 || len >= sizeof(templ->linkname))
 			goto error_out;
 			/* NOTREACHED */
 
@@ -222,8 +240,19 @@
 	if ((node->node_type == Nlink) && (node->flags & DEVFS_RULE_CREATED)) {
 		KKASSERT(node->link_target);
 		node->flags &= ~DEVFS_RULE_CREATED;
-		--node->link_target->nlinks;
-		devfs_gc(node);
+		/*
+		 * DF-3198: devfs_gc() frees arbitrary nodes (links pointing
+		 * at us, empty dirs) while devfs_iterate_topology() may still
+		 * hold them as cursors.  Only unlink here; the caller frees
+		 * the collected nodes after the walk completes.
+		 * devfs_unlinkp() takes care of the nlinks decrement and of
+		 * tearing down link_target.
+		 */
+		devfs_unlinkp(node);
+		if (unused != NULL) {
+			TAILQ_INSERT_TAIL((devfs_node_list_t *)unused,
+					  node, link);
+		}
 	} else if ((node->node_type == Ndev) && (node->d_dev)) {
 		node->uid = node->d_dev->si_uid;
 		node->gid = node->d_dev->si_gid;
@@ -238,18 +267,34 @@
 {
 	size_t len = 0;
 	char *path = NULL;
-	char *name, name_buf[PATH_MAX], buf[PATH_MAX];
+	char *name;
+	/*
+	 * DF-3201: these used to be PATH_MAX stack arrays; combined with
+	 * the rule-application recursion they exhausted the kernel stack.
+	 */
+	char *name_buf = kmalloc(PATH_MAX, M_DEVFS, M_WAITOK);
+	char *buf = kmalloc(PATH_MAX, M_DEVFS, M_WAITOK);
 
-	if (rule->name[rule->namlen-1] == '*') {
+	if (rule->namlen > 0 && rule->name[rule->namlen-1] == '*') {
 		devfs_resolve_name_path(rule->name, name_buf, &path, &name);
 		len = strlen(name);
 		--len;
+		/*
+		 * DF-3199: the pattern is not necessarily a plain prefix of
+		 * the node name; never strip more bytes than the node name
+		 * holds (d_name is a separate exact-size kmalloc() buffer).
+		 */
+		if (len > node->d_dir.d_namlen)
+			len = node->d_dir.d_namlen;
 		ksnprintf(buf, sizeof(buf), "%s%s",
 		    rule->linkname, node->d_dir.d_name+len);
 		devfs_alias_create(buf, node, 1);
 	} else {
 		devfs_alias_create(rule->linkname, node, 1);
 	}
+
+	kfree(name_buf, M_DEVFS);
+	kfree(buf, M_DEVFS);
 }
 
 void *
@@ -257,6 +302,7 @@
 {
 	struct devfs_rule *rule;
 	struct mount *mp = node->mp;
+	static int rule_link_depth = 0;	/* under devfs_lock + devfs_rule_lock */
 	int locked = 0;
 
 	/* Check if it is locked already. if not, we acquire the devfs lock */
@@ -332,8 +378,16 @@
 			/*
 			 * This is a LINK rule, so we tell devfs to create
 			 * a link with the correct name to this node.
+			 *
+			 * DF-3201: a rule whose own output matches itself
+			 * recurses without bound through allocp ->
+			 * check_apply -> create_link; cap the depth.
 			 */
-			devfs_rule_create_link(node, rule);
+			if (rule_link_depth < 8) {
+				++rule_link_depth;
+				devfs_rule_create_link(node, rule);
+				--rule_link_depth;
+			}
 #if 0
 			devfs_alias_create(rule->linkname, node, 1);
 #endif
@@ -361,19 +415,26 @@
 devfs_rule_checkname(struct devfs_rule *rule, struct devfs_node *node)
 {
 	struct devfs_node *parent = DEVFS_MNTDATA(node->mp)->root_node;
+	struct devfs_node *res;
 	char *path = NULL;
-	char *name, name_buf[PATH_MAX];
+	char *name;
+	char *name_buf = kmalloc(PATH_MAX, M_DEVFS, M_WAITOK);	/* DF-3201 */
 	int no_match = 0;
 
 	devfs_resolve_name_path(rule->name, name_buf, &path, &name);
-	parent = devfs_resolve_or_create_path(parent, path, 0);
+	res = devfs_resolve_or_create_path(parent, path, 0);
 
-	if (parent == NULL)
+	if (res == NULL) {
+		kfree(name_buf, M_DEVFS);
 		return 0; /* no match */
+	}
+	parent = res;
 
 	/* Check if node is a child of the parent we found */
-	if (node->parent != parent)
+	if (node->parent != parent) {
+		kfree(name_buf, M_DEVFS);
 		return 0; /* no match */
+	}
 
 #if 0
 	if (rule->rule_type & DEVFS_RULE_LINK)
@@ -382,6 +443,7 @@
 #endif
 	no_match = devfs_WildCaseCmp(name, node->d_dir.d_name);
 
+	kfree(name_buf, M_DEVFS);
 	return !no_match;
 }
 
@@ -424,6 +486,16 @@
 	error = 0;
 	rule = (struct devfs_rule_ioctl *)ap->a_data;
 
+	/*
+	 * DF-3197: rule state is GLOBAL (devfs_rule_list) and APPLY/RESET
+	 * walk every devfs mount on the system.  Do not let jailed root
+	 * rewrite host (or other jails') device visibility/permissions; the
+	 * 0600 /dev/devfs node is not a jail boundary (jailed uid 0 passes
+	 * the vnode permission check).
+	 */
+	if (jailed(curthread->td_ucred))
+		return (EPERM);
+
 	switch(ap->a_cmd) {
 	case DEVFS_RULE_ADD:
 		error = devfs_rule_insert(rule);
--- a/sys/vfs/devfs/devfs_core.c	2026-09-08 10:19:34.389993596 +0000
+++ b/sys/vfs/devfs/devfs_core.c	2026-09-08 10:21:51.856228023 +0000
@@ -1908,6 +1908,38 @@
  * reset_rules_worker) on all mountpoints or only a specific
  * one.
  */
+/*
+ * DF-3198: two-pass rule reset.  The topology walk only unlinks
+ * rule-created link nodes and collects them (see devfs_rule_reset_node);
+ * all freeing -- including nodes other collected links point at -- happens
+ * after the walk, so no devfs_iterate_topology() cursor can reference
+ * freed memory.  Collected nodes already freed by an earlier links pass
+ * are recognized by DEVFS_DESTROYED (their memory stays stable: this runs
+ * on the devfs core thread and nothing on this path allocates from the
+ * devfs_node zone).
+ */
+static void
+devfs_reset_mount(struct devfs_node *root)
+{
+	typedef TAILQ_HEAD(, devfs_node) devfs_dead_list_t;
+	devfs_dead_list_t dead = TAILQ_HEAD_INITIALIZER(dead);
+	struct devfs_node *n1, *n2;
+
+	devfs_iterate_topology(root, devfs_rule_reset_node, &dead);
+
+	TAILQ_FOREACH_MUTABLE(n1, &dead, link, n2) {
+		if (n1->flags & DEVFS_DESTROYED)
+			continue;
+		if (n1->nlinks > 0)
+			devfs_iterate_topology(root,
+				(devfs_iterate_callback_t *)devfs_gc_links_callback,
+				n1);
+		devfs_freep(n1);
+	}
+	devfs_iterate_topology(root,
+			(devfs_iterate_callback_t *)devfs_gc_dirs_callback, NULL);
+}
+
 static int
 devfs_apply_reset_rules_caller(char *mountto, int apply)
 {
@@ -1915,16 +1947,22 @@
 
 	if (mountto[0] == '*') {
 		TAILQ_FOREACH(mnt, &devfs_mnt_list, link) {
-			devfs_iterate_topology(mnt->root_node,
-					(apply)?(devfs_rule_check_apply):(devfs_rule_reset_node),
-					NULL);
+			if (apply) {
+				devfs_iterate_topology(mnt->root_node,
+						devfs_rule_check_apply, NULL);
+			} else {
+				devfs_reset_mount(mnt->root_node);
+			}
 		}
 	} else {
 		TAILQ_FOREACH(mnt, &devfs_mnt_list, link) {
 			if (!strcmp(mnt->mp->mnt_stat.f_mntonname, mountto)) {
-				devfs_iterate_topology(mnt->root_node,
-					(apply)?(devfs_rule_check_apply):(devfs_rule_reset_node),
-					NULL);
+				if (apply) {
+					devfs_iterate_topology(mnt->root_node,
+							devfs_rule_check_apply, NULL);
+				} else {
+					devfs_reset_mount(mnt->root_node);
+				}
 				break;
 			}
 		}