[PATCH v5 1/9] of: hold a reference on of_aliases during alias path resolution
From: Abdurrahman Hussain
Date: Wed Jul 22 2026 - 23:24:39 EST
of_find_node_opts_by_path() walks the property list of of_aliases
without taking a reference on the node and passes pp->value straight
to of_find_node_by_path().
Take a reference across the walk. The walk itself stays lock-free
like every other property iteration: removed properties keep their
->next and are only freed when the node is released, so the node
reference is sufficient. devtree_lock covers only the pointer load,
pairing it with a later patch in this series that clears of_aliases
and drops its reference when the node is detached at runtime.
Validate the value before resolving it. of_alias_value_ok() requires
a non-empty, NUL-terminated, absolute path:
- an empty property has a NULL value and crashes in strchr()
- a value without a NUL inside the property is read past its end
- a relative value naming another alias (loop = "loop") recurses
through of_find_node_by_path() until the stack is exhausted
All three are reachable with a malformed boot FDT today.
The name comparison loses its redundant strlen() pass while here.
Assisted-by: Claude:claude-fable-5 [Claude Code]
Signed-off-by: Abdurrahman Hussain <abdurrahman@xxxxxxxxxx>
---
drivers/of/base.c | 20 +++++++++++++++-----
drivers/of/of_private.h | 8 ++++++++
2 files changed, 23 insertions(+), 5 deletions(-)
diff --git a/drivers/of/base.c b/drivers/of/base.c
index 6e7a42dedad3..6f79f593fc77 100644
--- a/drivers/of/base.c
+++ b/drivers/of/base.c
@@ -995,6 +995,8 @@ struct device_node *of_find_node_opts_by_path(const char *path, const char **opt
/* The path could begin with an alias */
if (*path != '/') {
+ struct device_node *aliases;
+ const char *value = NULL;
int len;
const char *p = strchrnul(path, '/');
@@ -1002,16 +1004,24 @@ struct device_node *of_find_node_opts_by_path(const char *path, const char **opt
p = separator;
len = p - path;
- /* of_aliases must not be NULL */
- if (!of_aliases)
+ /* the load pairs with writers that retire the node */
+ raw_spin_lock_irqsave(&devtree_lock, flags);
+ aliases = of_node_get(of_aliases);
+ raw_spin_unlock_irqrestore(&devtree_lock, flags);
+ if (!aliases)
return NULL;
- for_each_property_of_node(of_aliases, pp) {
- if (strlen(pp->name) == len && !strncmp(pp->name, path, len)) {
- np = of_find_node_by_path(pp->value);
+ for_each_property_of_node(aliases, pp) {
+ if (!strncmp(pp->name, path, len) && !pp->name[len]) {
+ if (of_alias_value_ok(pp))
+ value = pp->value;
break;
}
}
+ /* the reference on @aliases keeps @value alive */
+ if (value)
+ np = of_find_node_by_path(value);
+ of_node_put(aliases);
if (!np)
return NULL;
path = p;
diff --git a/drivers/of/of_private.h b/drivers/of/of_private.h
index 0ae16da066e2..9bba999f0bf8 100644
--- a/drivers/of/of_private.h
+++ b/drivers/of/of_private.h
@@ -215,6 +215,14 @@ static inline bool is_pseudo_property(const char *prop_name)
!of_prop_cmp(prop_name, "linux,phandle");
}
+/* alias values must be absolute paths NUL-terminated within length */
+static inline bool of_alias_value_ok(const struct property *pp)
+{
+ return pp->value && pp->length >= 2 &&
+ *(const char *)pp->value == '/' &&
+ strnlen(pp->value, pp->length) < pp->length;
+}
+
#if IS_ENABLED(CONFIG_KUNIT)
int __of_address_resource_bounds(struct resource *r, u64 start, u64 size);
#endif
--
2.54.0