All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] libfdt: Check node names in one pass in fdt_next_tag()
@ 2026-11-02  2:30 Kelvin Zhang
  2026-11-02  4:21 ` David Gibson
  0 siblings, 1 reply; 4+ messages in thread
From: Kelvin Zhang @ 2026-11-02  2:30 UTC (permalink / raw)
  To: devicetree-compiler; +Cc: David Gibson, Simon Glass

fdt_next_tag() skips the name of a node by calling fdt_offset_ptr()
for each byte. Unless FDT_ASSUME_MASK includes ASSUME_VALID_INPUT,
each call reads totalsize, off_dt_struct, version and size_dt_struct
from the header again and checks the access against them. Every walk
over the structure block skips the name of every node it passes, so
this dominates lookups such as fdt_path_offset() in large trees.

The structure block offset maps linearly to a pointer, so work out
once per call where the range that fdt_offset_ptr() accepts ends.
Check each access against that with plain comparisons, and find the
end of the name with one memchr() over the rest of the range. The
checks themselves don't change: a name that isn't terminated before
the end of the structure block still makes fdt_next_tag() return
FDT_END. The range is also capped at INT_MAX, since offsets are ints,
so a header that claims more than 2 GiB now ends the walk instead of
producing a negative next offset.

With a 612 KiB tree (3,012 nodes) on x86-64, built with gcc -O2 and
the default FDT_ASSUME_MASK, a full walk with fdt_next_tag() takes
122 us instead of 297 us, and fdt_path_offset() of /chosen, which is
near the end, 148 us instead of 434 us. In Android's Generic
Bootloader on an AArch64 device at 600 MHz, also with the default
FDT_ASSUME_MASK, the fixups made to that tree after applying overlays
take 23.3 ms instead of 33.1 ms.

Signed-off-by: Kelvin Zhang <zhangkelvin@google.com>
---
 libfdt/fdt.c | 63 ++++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 54 insertions(+), 9 deletions(-)

diff --git a/libfdt/fdt.c b/libfdt/fdt.c
index 56d4dcb..7325aa2 100644
--- a/libfdt/fdt.c
+++ b/libfdt/fdt.c
@@ -167,15 +167,59 @@ const void *fdt_offset_ptr(const void *fdt, int
offset, unsigned int len)
 	return fdt_offset_ptr_(fdt, offset);
 }

+/*
+ * End offset (exclusive, relative to the structure block) of the range that
+ * fdt_offset_ptr() accepts, capped at INT_MAX, so that callers making several
+ * accesses can check each of them with plain comparisons instead of reading
+ * the header again.
+ */
+static unsigned int fdt_struct_limit_(const void *fdt)
+{
+	unsigned int limit = INT_MAX;
+
+	if (!can_assume(VALID_INPUT)) {
+		unsigned int base = fdt_off_dt_struct(fdt);
+		unsigned int totalsize = fdt_totalsize(fdt);
+		unsigned int avail = (base <= totalsize) ? totalsize - base : 0;
+
+		if (avail < limit)
+			limit = avail;
+	}
+
+	if (can_assume(LATEST) || fdt_version(fdt) >= 0x11) {
+		unsigned int size = fdt_size_dt_struct(fdt);
+
+		if (size < limit)
+			limit = size;
+	}
+
+	return limit;
+}
+
+/*
+ * Checks @offset and @len > 0 like fdt_offset_ptr() does, against
+ * @limit = fdt_struct_limit_(@fdt).
+ */
+static const void *fdt_offset_ptr_limit_(const void *fdt, unsigned int limit,
+					 int offset, unsigned int len)
+{
+	if ((offset < 0) || (len > limit)
+	    || ((unsigned int)offset > limit - len))
+		return NULL;
+
+	return fdt_offset_ptr_(fdt, offset);
+}
+
 uint32_t fdt_next_tag(const void *fdt, int startoffset, int *nextoffset)
 {
 	const fdt32_t *tagp, *lenp;
 	uint32_t tag, len, sum;
+	unsigned int limit = fdt_struct_limit_(fdt);
 	int offset = startoffset;
-	const char *p;
+	const char *p, *nul;

 	*nextoffset = -FDT_ERR_TRUNCATED;
-	tagp = fdt_offset_ptr(fdt, offset, FDT_TAGSIZE);
+	tagp = fdt_offset_ptr_limit_(fdt, limit, offset, FDT_TAGSIZE);
 	if (!can_assume(VALID_DTB) && !tagp)
 		return FDT_END; /* premature end */
 	tag = fdt32_to_cpu(*tagp);
@@ -184,16 +228,16 @@ uint32_t fdt_next_tag(const void *fdt, int
startoffset, int *nextoffset)
 	*nextoffset = -FDT_ERR_BADSTRUCTURE;
 	switch (tag) {
 	case FDT_BEGIN_NODE:
-		/* skip name */
-		do {
-			p = fdt_offset_ptr(fdt, offset++, 1);
-		} while (p && (*p != '\0'));
-		if (!can_assume(VALID_DTB) && !p)
+		/* skip name: check the rest of the block once, then scan it */
+		p = fdt_offset_ptr_limit_(fdt, limit, offset, 1);
+		nul = p ? memchr(p, '\0', limit - offset) : NULL;
+		if (!nul)
 			return FDT_END; /* premature end */
+		offset += nul - p + 1;
 		break;

 	case FDT_PROP:
-		lenp = fdt_offset_ptr(fdt, offset, sizeof(*lenp));
+		lenp = fdt_offset_ptr_limit_(fdt, limit, offset, sizeof(*lenp));
 		if (!can_assume(VALID_DTB) && !lenp)
 			return FDT_END; /* premature end */

@@ -221,7 +265,8 @@ uint32_t fdt_next_tag(const void *fdt, int
startoffset, int *nextoffset)
 		return FDT_END;
 	}

-	if (!fdt_offset_ptr(fdt, startoffset, offset - startoffset))
+	if (!fdt_offset_ptr_limit_(fdt, limit, startoffset,
+				   offset - startoffset))
 		return FDT_END; /* premature end */

 	*nextoffset = FDT_TAGALIGN(offset);

base-commit: c22868416b8db8354ce5db29d3494dda688a2ae3
-- 
2.56.0.385.gac671c819a-goog

^ permalink raw reply related	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-11-02  5:11 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-02  2:30 [PATCH] libfdt: Check node names in one pass in fdt_next_tag() Kelvin Zhang
2026-11-02  4:21 ` David Gibson
2026-11-02  4:50   ` Kelvin Zhang
2026-11-02  4:59     ` David Gibson

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.