diff --git a/.gitignore b/.gitignore index a75fcf7e..659cabc9 100644 --- a/.gitignore +++ b/.gitignore @@ -197,6 +197,7 @@ tools/squashelf/** !tools/squashelf/squashelf.c !tools/squashelf/Makefile !tools/squashelf/README.md +!tools/squashelf/test-range-overflow.py # Generated configuration files .config diff --git a/tools/squashelf/Makefile b/tools/squashelf/Makefile index 675c3906..cb6ec5b5 100644 --- a/tools/squashelf/Makefile +++ b/tools/squashelf/Makefile @@ -18,13 +18,16 @@ else CFLAGS+=$(OPTIMIZE) endif -.PHONY: clean all debug +.PHONY: clean all debug test all: $(TARGET) debug: CFLAGS+=$(DEBUG_FLAGS) debug: all +test: $(TARGET) + python3 test-range-overflow.py ./$(TARGET) + $(TARGET): $(TARGET).o @echo "Building squashelf tool" $(CC) -o $@ $< $(LDFLAGS) $(CFLAGS_EXTRA) diff --git a/tools/squashelf/squashelf.c b/tools/squashelf/squashelf.c index 38b3f41e..cffb2e12 100644 --- a/tools/squashelf/squashelf.c +++ b/tools/squashelf/squashelf.c @@ -560,7 +560,27 @@ int main(int argCount, char** argValues) /* Apply range filter if specified */ if (hasRange) { uint64_t segmentStart = p_paddr; - uint64_t segmentEnd = p_paddr + p_memsz - 1; + uint64_t segmentEnd; + + /* Guard against uint64_t overflow when computing the segment + * end (CWE-190). Both fields come straight from the (possibly + * crafted) program header; if p_paddr + p_memsz - 1 wrapped, the + * range check could spuriously include an out-of-range segment + * or drop a valid one. Treat such a segment as out-of-range. */ + if (p_memsz == 0) { + segmentEnd = p_paddr; + } + else if (p_paddr > UINT64_MAX - (p_memsz - 1)) { + fprintf(stderr, + "Skipping segment %zu (LMA 0x%lx, size 0x%lx) - " + "address range overflows 64-bit space\n", + i, (unsigned long)p_paddr, + (unsigned long)p_memsz); + continue; + } + else { + segmentEnd = p_paddr + p_memsz - 1; + } /* Check if segment start and end are both within any range */ bool startInRange = diff --git a/tools/squashelf/test-range-overflow.py b/tools/squashelf/test-range-overflow.py new file mode 100644 index 00000000..db0747a9 --- /dev/null +++ b/tools/squashelf/test-range-overflow.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +# test-range-overflow.py +# +# Regression test for the uint64_t overflow in squashelf's range filter +# (segmentEnd = p_paddr + p_memsz - 1). A crafted ELF64 PT_LOAD segment whose +# p_paddr + p_memsz wraps past 2^64 must NOT be smuggled past a range filter: +# its true span covers (almost) the whole address space, so it is out of range +# and must be excluded. Before the fix the wrapped end landed back inside the +# range and the segment was wrongly kept. +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfBoot. +# +# wolfBoot is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# wolfBoot is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +import os +import struct +import subprocess +import sys +import tempfile + +EHSIZE = 64 +PHSIZE = 56 +UINT64_MAX = 0xFFFFFFFFFFFFFFFF + + +def make_elf64(path, paddr, memsz, filesz=0x10): + ph_off = EHSIZE + seg_off = ph_off + PHSIZE + ident = b"\x7fELF" + bytes([2, 1, 1, 0]) + b"\x00" * 8 # ELF64, little-endian + ehdr = ident + struct.pack( + " 1 else "./squashelf" + d = tempfile.mkdtemp() + rc = 0 + + # 1) Overflow segment: p_paddr + p_memsz - 1 wraps below p_paddr, so the + # wrapped end (~0x4fe) is inside [0, 0x1000] even though the real span + # covers the whole address space. It MUST be excluded (non-zero exit, + # no output segment). + bad = os.path.join(d, "overflow.elf") + make_elf64(bad, paddr=0x500, memsz=UINT64_MAX) + if run(squashelf, bad, os.path.join(d, "bad.out"), "0x0-0x1000") == 0: + print("FAIL: overflow segment was wrongly included by range filter") + rc = 1 + else: + print("PASS: overflow segment excluded") + + # 2) Regression guard: a normal in-range segment must still be kept. + good = os.path.join(d, "ok.elf") + make_elf64(good, paddr=0x500, memsz=0x100) + if run(squashelf, good, os.path.join(d, "good.out"), "0x0-0x1000") != 0: + print("FAIL: normal in-range segment was dropped") + rc = 1 + else: + print("PASS: normal in-range segment kept") + + sys.exit(rc) + + +if __name__ == "__main__": + main()