Skip to content

Signed integer overflow in fdt_next_tag() on a crafted FDT blob (libfdt/fdt.c) #188

Description

@OwenSanzas

Summary

When fdt_next_tag() traverses a malformed Flattened Device Tree (FDT) blob, it computes a structure-block offset from an attacker-controlled tag/length field without adequate bounds checking. On a crafted input the running offset is driven to a near-INT_MIN value, and the subsequent offset - startoffset computation overflows signed int, which is undefined behavior. UBSan reports the fault as fatal. This is reachable through the public libfdt traversal API on any untrusted FDT blob.

Note: the actual fault is a signed-integer-overflow, not a misaligned access.

Confirmed reproducing on current main (HEAD 66e1201, 2026-06-17).

Root Cause

fdt_next_tag() advances offset based on the parsed tag and an attacker-controlled length, then computes offset - startoffset to validate the consumed region. Because offset can be pushed to a very large negative value via the crafted length, this subtraction overflows the signed int range.

libfdt/fdt.c (fdt_next_tag, around line 216):

uint32_t fdt_next_tag(const void *fdt, int startoffset, int *nextoffset)
{
	const fdt32_t *tagp, *lenp;
	uint32_t tag, len, sum;
	int offset = startoffset;
	...
	case FDT_PROP:
		...
		len = fdt32_to_cpu(*lenp);
		sum = len + offset;
		if (!can_assume(VALID_DTB) &&
		    (INT_MAX <= sum || sum < (uint32_t) offset))
			return FDT_END; /* premature end */

		/* skip-name offset, length and value */
		offset += sizeof(struct fdt_property) - FDT_TAGSIZE + len;
		...
	}

	if (!fdt_offset_ptr(fdt, startoffset, offset - startoffset))   // line 216: offset - startoffset overflows int
		return FDT_END; /* premature end */

	*nextoffset = FDT_TAGALIGN(offset);
	return tag;
}

The overflow occurs in the offset - startoffset expression on line 216 (column 47).

PoC

A 60-byte crafted FDT blob (poc.bin) that drives offset to a near-INT_MIN value when parsed by the libfdt traversal API.

Reproduction

Build libfdt from source with UBSan and traverse the attached blob through the public FDT API
(fdt_next_tag is reached by any traversal, e.g. fdt_first_subnode/fdt_next_node):

clang -fsanitize=undefined -fno-sanitize-recover -Ilibfdt -c libfdt/fdt*.c
# then call fdt_next_tag()/fdt_first_subnode() over poc.bin (recreate it from the base64 below)

Output:

libfdt/fdt.c:224:47: runtime error: signed integer overflow: -2147483642 - 8 cannot be represented in type 'int'

Confirmed on current main (HEAD 66e1201, 2026-06-17).

Suggested Fix

Perform the offset arithmetic and the consumed-region length using unsigned/wider types and bounds-check before subtraction, so that offset - startoffset can never overflow signed int. Reject the blob (return FDT_END) when the computed offset is out of the valid [startoffset, INT_MAX] range instead of forming the difference in int.

PoC bytes (self-contained)

The trigger input is 60 bytes (poc/poc.bin).
Recreate it exactly with:

base64 -d > poc.bin <<'B64'
0A3+7X////8AAAAoAAAAPAAAACgAAAARAAAAEAAAAAAAAAAAf///1wAAAAEAAAAAAAAAA3////IAAAAA
B64

Hex: d00dfeed7fffffff000000280000003c00000028000000110000001000000000000000007fffffd70000000100000000000000037ffffff200000000

Credit

Aisle Research (Ze Sheng (O2Lab & TAMU), Dmitrijs Trizna, Luigino Camastra, Guido Vranken).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions