mirror of https://github.com/wolfSSL/wolfBoot.git
F-4692: guard squashelf segment-offset alignment against integer overflow
The alignment round-up current_offset = (current_offset + p_align - 1) & ~(p_align - 1) used p_align straight from a (possibly crafted) program header without bounds. For ELF64 a p_align near UINT64_MAX wraps the sum to a tiny value (e.g. 0x78 -> 0); for ELF32 a value that rounds the offset past 2^32 is silently truncated when stored into the uint32_t offset field. Either way segment data is written at a wrong (often zero) file offset, clobbering the output ELF header while squashelf still reports success. Reject such inputs: bound the round-up against UINT64_MAX before applying it (both classes) and reject an ELF32 offset that exceeds UINT32_MAX. Normal page-aligned segments are unaffected. Adds test-align-overflow.py covering the ELF64 wrap, the ELF32 truncation, and the normal-segment regression.pull/792/head
parent
93a8fe8d5a
commit
0f01a9d831
|
|
@ -207,6 +207,7 @@ tools/squashelf/**
|
|||
!tools/squashelf/Makefile
|
||||
!tools/squashelf/README.md
|
||||
!tools/squashelf/test-range-overflow.py
|
||||
!tools/squashelf/test-align-overflow.py
|
||||
|
||||
# Generated configuration files
|
||||
.config
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ debug: all
|
|||
|
||||
test: $(TARGET)
|
||||
python3 test-range-overflow.py ./$(TARGET)
|
||||
python3 test-align-overflow.py ./$(TARGET)
|
||||
|
||||
$(TARGET): $(TARGET).o
|
||||
@echo "Building squashelf tool"
|
||||
|
|
|
|||
|
|
@ -752,10 +752,31 @@ int main(int argCount, char** argValues)
|
|||
/* Align the segment according to its alignment requirement if
|
||||
* needed */
|
||||
if (p_align > 1) {
|
||||
/* Guard against overflow in the round-up (CWE-190). p_align
|
||||
* comes straight from a possibly crafted program header; a
|
||||
* large value would wrap current_offset to a small value and
|
||||
* place this segment's data over the ELF header. */
|
||||
if (current_offset > UINT64_MAX - (p_align - 1)) {
|
||||
fprintf(stderr,
|
||||
"Segment %zu alignment 0x%lx overflows file "
|
||||
"offset 0x%lx\n",
|
||||
i, (unsigned long)p_align,
|
||||
(unsigned long)current_offset);
|
||||
goto cleanup;
|
||||
}
|
||||
current_offset =
|
||||
(current_offset + p_align - 1) & ~(p_align - 1);
|
||||
}
|
||||
|
||||
/* The 32-bit program header offset is a uint32_t; a larger value
|
||||
* would be silently truncated and corrupt the output layout. */
|
||||
if (current_offset > UINT32_MAX) {
|
||||
fprintf(stderr,
|
||||
"Segment %zu offset 0x%lx exceeds 32-bit ELF range\n",
|
||||
i, (unsigned long)current_offset);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Update the segment's offset */
|
||||
ph32_array[i].offset = current_offset;
|
||||
p_filesz = ph32_array[i].file_size;
|
||||
|
|
@ -770,6 +791,18 @@ int main(int argCount, char** argValues)
|
|||
/* Align the segment according to its alignment requirement if
|
||||
* needed */
|
||||
if (p_align > 1) {
|
||||
/* Guard against overflow in the round-up (CWE-190). p_align
|
||||
* comes straight from a possibly crafted program header; a
|
||||
* value near UINT64_MAX would wrap current_offset to a small
|
||||
* value and place this segment's data over the ELF header. */
|
||||
if (current_offset > UINT64_MAX - (p_align - 1)) {
|
||||
fprintf(stderr,
|
||||
"Segment %zu alignment 0x%lx overflows file "
|
||||
"offset 0x%lx\n",
|
||||
i, (unsigned long)p_align,
|
||||
(unsigned long)current_offset);
|
||||
goto cleanup;
|
||||
}
|
||||
current_offset =
|
||||
(current_offset + p_align - 1) & ~(p_align - 1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,160 @@
|
|||
#!/usr/bin/env python3
|
||||
# test-align-overflow.py
|
||||
#
|
||||
# Regression test for the integer overflow in squashelf's segment-offset
|
||||
# alignment round-up (current_offset = (current_offset + p_align - 1) &
|
||||
# ~(p_align - 1)). p_align comes straight from a possibly crafted program
|
||||
# header. A value near UINT64_MAX (ELF64) wraps the sum to a tiny value, and a
|
||||
# value that rounds the offset past 2^32 (ELF32) is silently truncated to a
|
||||
# uint32_t. Either way the segment data lands at a wrong (often zero) file
|
||||
# offset, clobbering the ELF header while squashelf still reports success.
|
||||
# After the fix squashelf must reject such inputs with a non-zero exit instead
|
||||
# of writing a corrupt output ELF.
|
||||
#
|
||||
# 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
|
||||
|
||||
EHSIZE64 = 64
|
||||
PHSIZE64 = 56
|
||||
EHSIZE32 = 52
|
||||
PHSIZE32 = 32
|
||||
UINT64_MAX = 0xFFFFFFFFFFFFFFFF
|
||||
UINT32_MAX = 0xFFFFFFFF
|
||||
|
||||
|
||||
def make_elf64(path, align, filesz=0x10):
|
||||
ph_off = EHSIZE64
|
||||
seg_off = ph_off + PHSIZE64
|
||||
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
|
||||
EHSIZE64, # header_size
|
||||
PHSIZE64, # ph_entry_size
|
||||
1, # ph_entry_count
|
||||
0, 0, 0)
|
||||
phdr = struct.pack(
|
||||
"<IIQQQQQQ",
|
||||
1, # PT_LOAD
|
||||
5, # flags R+X
|
||||
seg_off, # offset
|
||||
0x1000, # vaddr
|
||||
0x1000, # paddr
|
||||
filesz, # file_size
|
||||
filesz, # mem_size
|
||||
align) # align
|
||||
with open(path, "wb") as f:
|
||||
f.write(ehdr + phdr + b"\xAA" * filesz)
|
||||
|
||||
|
||||
def make_elf32(path, align, filesz=0x10):
|
||||
ph_off = EHSIZE32
|
||||
seg_off = ph_off + PHSIZE32
|
||||
ident = b"\x7fELF" + bytes([1, 1, 1, 0]) + b"\x00" * 8 # ELF32, little-endian
|
||||
ehdr = ident + struct.pack(
|
||||
"<HHIIIIIHHHHHH",
|
||||
2, # type ET_EXEC
|
||||
3, # machine x86
|
||||
1, # version
|
||||
0, # entry
|
||||
ph_off, # ph_offset
|
||||
0, # sh_offset
|
||||
0, # flags
|
||||
EHSIZE32, # header_size
|
||||
PHSIZE32, # ph_entry_size
|
||||
1, # ph_entry_count
|
||||
0, 0, 0)
|
||||
phdr = struct.pack(
|
||||
"<IIIIIIII",
|
||||
1, # PT_LOAD
|
||||
seg_off, # offset
|
||||
0x1000, # vaddr
|
||||
0x1000, # paddr
|
||||
filesz, # file_size
|
||||
filesz, # mem_size
|
||||
5, # flags R+X
|
||||
align) # align
|
||||
with open(path, "wb") as f:
|
||||
f.write(ehdr + phdr + b"\xAA" * filesz)
|
||||
|
||||
|
||||
def run(squashelf, infile, outfile):
|
||||
return subprocess.run(
|
||||
[squashelf, infile, outfile],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode
|
||||
|
||||
|
||||
def main():
|
||||
squashelf = sys.argv[1] if len(sys.argv) > 1 else "./squashelf"
|
||||
rc = 0
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
# 1) ELF64 alignment near UINT64_MAX wraps the round-up to a tiny
|
||||
# offset. squashelf MUST reject it (non-zero exit), not write a
|
||||
# corrupt ELF over its own header and report success.
|
||||
bad64 = os.path.join(d, "align64.elf")
|
||||
out64 = os.path.join(d, "align64.out")
|
||||
make_elf64(bad64, align=UINT64_MAX)
|
||||
if run(squashelf, bad64, out64) == 0:
|
||||
print("FAIL: ELF64 overflowing alignment was accepted")
|
||||
rc = 1
|
||||
else:
|
||||
print("PASS: ELF64 overflowing alignment rejected")
|
||||
|
||||
# 2) ELF32 alignment that rounds the offset past 2^32; the uint32_t
|
||||
# offset field would silently truncate (to 0 here). Must be rejected.
|
||||
bad32 = os.path.join(d, "align32.elf")
|
||||
out32 = os.path.join(d, "align32.out")
|
||||
make_elf32(bad32, align=UINT32_MAX)
|
||||
if run(squashelf, bad32, out32) == 0:
|
||||
print("FAIL: ELF32 truncating alignment was accepted")
|
||||
rc = 1
|
||||
else:
|
||||
print("PASS: ELF32 truncating alignment rejected")
|
||||
|
||||
# 3) Regression guard: normal page-aligned segments must still succeed.
|
||||
ok64 = os.path.join(d, "ok64.elf")
|
||||
ok64o = os.path.join(d, "ok64.out")
|
||||
make_elf64(ok64, align=0x1000)
|
||||
if run(squashelf, ok64, ok64o) != 0:
|
||||
print("FAIL: normal ELF64 segment was rejected")
|
||||
rc = 1
|
||||
else:
|
||||
print("PASS: normal ELF64 segment kept")
|
||||
|
||||
ok32 = os.path.join(d, "ok32.elf")
|
||||
ok32o = os.path.join(d, "ok32.out")
|
||||
make_elf32(ok32, align=0x1000)
|
||||
if run(squashelf, ok32, ok32o) != 0:
|
||||
print("FAIL: normal ELF32 segment was rejected")
|
||||
rc = 1
|
||||
else:
|
||||
print("PASS: normal ELF32 segment kept")
|
||||
|
||||
sys.exit(rc)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in New Issue