mirror of https://github.com/wolfSSL/wolfBoot.git
F-4789: fix uint64_t overflow in squashelf range filter segment end
In the PT_LOAD range filter, segmentEnd was computed as p_paddr + p_memsz - 1 in uint64_t with no overflow check. Both fields come straight from the (possibly crafted) ELF64 program header. When p_paddr + p_memsz exceeds 2^64 the result wraps below p_paddr: the wrapped end can land back inside the requested range, so a segment whose true span lies entirely outside the range is spuriously included (and the converse can silently drop a valid segment). squashelf would then emit a squashed image that wolfBoot signs and boots. Detect the overflow before computing segmentEnd and treat such a segment as out-of-range. ELF32 cannot overflow a uint64 sum and is unaffected. Adds tools/squashelf/test-range-overflow.py (run via `make test`) which fails before the fix and passes after.pull/791/head
parent
740383a3f5
commit
f193b9c239
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
"<HHIQQQIHHHHHH",
|
||||
2, # type ET_EXEC
|
||||
62, # machine x86-64
|
||||
1, # version
|
||||
0, # entry
|
||||
ph_off, # ph_offset
|
||||
0, # sh_offset
|
||||
0, # flags
|
||||
EHSIZE, # header_size
|
||||
PHSIZE, # ph_entry_size
|
||||
1, # ph_entry_count
|
||||
0, 0, 0)
|
||||
phdr = struct.pack(
|
||||
"<IIQQQQQQ",
|
||||
1, # PT_LOAD
|
||||
5, # flags R+X
|
||||
seg_off, # offset
|
||||
paddr, # vaddr
|
||||
paddr, # paddr
|
||||
filesz, # file_size
|
||||
memsz, # mem_size
|
||||
0x1000) # align
|
||||
with open(path, "wb") as f:
|
||||
f.write(ehdr + phdr + b"\xAA" * filesz)
|
||||
|
||||
|
||||
def run(squashelf, infile, outfile, rng):
|
||||
return subprocess.run(
|
||||
[squashelf, "-r", rng, infile, outfile],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode
|
||||
|
||||
|
||||
def main():
|
||||
squashelf = sys.argv[1] if len(sys.argv) > 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()
|
||||
Loading…
Reference in New Issue