--- 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 #include #include +#include #include #include @@ -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; } }