From 1f21b45361fa8cae0a69f2ce549c26058b20c172 Mon Sep 17 00:00:00 2001 From: Kovalev Vladislav Date: Mon, 26 Oct 2026 16:53:27 +0600 Subject: [PATCH 1/2] fix(tools): keep SELF PT_PHDR tables in the load image --- tools/tests/test_unwrap_self.py | 77 +++++++++++++++++++++++++++++++++ tools/unwrap_self.py | 19 +++++++- 2 files changed, 94 insertions(+), 2 deletions(-) diff --git a/tools/tests/test_unwrap_self.py b/tools/tests/test_unwrap_self.py index a64f76f9abd2..250120681a35 100644 --- a/tools/tests/test_unwrap_self.py +++ b/tools/tests/test_unwrap_self.py @@ -20,6 +20,7 @@ PT_DYNAMIC = 2 PT_TLS = 7 PT_NOTE = 4 +PT_PHDR = 6 SCE_COMMENT = 0x6FFFFF00 SCE_VERSION = 0x6FFFFF01 FIRST_REGION = 0x400 @@ -849,6 +850,82 @@ def test_program_headers_do_not_overwrite_the_first_load_payload(self): self.assertEqual(len(headers), count) self.assertTrue(all(header[0] == 0 for header in headers[1:])) + def test_phdr_names_the_rewritten_table_in_the_load_image(self): + table_size = 2 * 56 + for address in (0, 0x4000): + for delta in (0, 0x40): + for recorded in (False, True): + with self.subTest(address=address, delta=delta, recorded=recorded): + table_offset = PAGE + delta + segments = [(PT_PHDR, 4, table_offset, address + delta, table_size, 8), + (PT_LOAD, 4, PAGE, address, 0x400, PAGE)] + table = build_elf(segments, table_offset)[table_offset:table_offset + table_size] + payload = bytearray(b"load payload\0" * 80)[:0x400] + payload[delta:delta + table_size] = table + records = [(0x100800, 0x3000, len(payload))] + if recorded: + records.insert(0, (0x800, 0x3000 + delta, table_size)) + container = build_container(segments, records, {0x3000: payload}, + program_offset=table_offset) + result, output = self.convert(container) + self.assertEqual(result.returncode, 0, result.stderr) + data, headers = read_program_headers(output) + phdr, load = headers + rewritten = struct.unpack_from(" tail[0]): tail = (header["offset"], header["source"] - header["offset"]) continue - if header["vaddr"] != 0: + if header["vaddr"] != 0 or header["type"] == PT_PHDR: host = load_for(header["vaddr"], header["size"]) if host is None: if header["size"] == 0: @@ -266,7 +267,18 @@ def unwrap(path, output, verbose=False): records = read_records(data) inner, elf_header, headers = parse_elf(data) + phdrs = [header for header in headers if header["type"] == PT_PHDR] + if len(phdrs) > 1: + raise Refused("more than one PT_PHDR segment") + phdr = phdrs[0] if phdrs else None + if phdr is not None: + if phdr["size"] < len(headers) * 56 or phdr["memory"] < len(headers) * 56: + raise Refused("PT_PHDR does not cover the program header table") + if any(header["type"] == PT_LOAD and header["index"] < phdr["index"] for header in headers): + raise Refused("PT_PHDR must precede the load segments") place_segments(data, headers, records) + if phdr is not None and phdr["host"] is None: + raise Refused("PT_PHDR is not in a file-backed load segment") check_decrypted(data, headers) regions = [] @@ -350,6 +362,9 @@ def output_region(header): if header["new"] + header["size"] > len(result): raise Refused("segment %d escaped the output" % header["index"]) + if phdr is not None: + program_offset = phdr["new"] + struct.pack_into(" Date: Wed, 28 Oct 2026 19:19:02 +0600 Subject: [PATCH 2/2] fix(tools): refuse mismatched SELF program header mappings --- tools/tests/test_unwrap_self.py | 75 ++++++++++++++++++++++++++++++--- tools/unwrap_self.py | 27 +++++++----- 2 files changed, 87 insertions(+), 15 deletions(-) diff --git a/tools/tests/test_unwrap_self.py b/tools/tests/test_unwrap_self.py index 250120681a35..2658c2e014fe 100644 --- a/tools/tests/test_unwrap_self.py +++ b/tools/tests/test_unwrap_self.py @@ -884,16 +884,22 @@ def test_phdr_names_the_rewritten_table_in_the_load_image(self): def test_invalid_phdr_metadata_preserves_input_and_output(self): load = (PT_LOAD, 4, PAGE, 0x4000, 0x400, PAGE) phdr = (PT_PHDR, 4, PAGE, 0x4000, 2 * 56, 8) - cases = [([phdr, phdr, load], None), ([load, phdr], None)] - cases += [([(PT_PHDR, 4, PAGE, 0x4000, size, 8), load], None) for size in (0, 2 * 56 - 1)] - cases += [([phdr, load], size) for size in (0, 2 * 56 - 1)] + duplicate = (PT_PHDR, 4, PAGE, 0x4000, 3 * 56, 8) + cases = [([duplicate, duplicate, load], None, "more than one PT_PHDR segment"), + ([load, phdr], None, "PT_PHDR must precede the load segments")] + cases += [([(PT_PHDR, 4, PAGE, 0x4000, size, 8), load], 2 * 56, + "PT_PHDR does not cover the program header table") for size in (0, 2 * 56 - 1)] + cases += [([phdr, load], size, "PT_PHDR does not cover the program header table") + for size in (0, 2 * 56 - 1)] directory = Path(self.directory) source, output = directory / "input.self", directory / "output.elf" - for segments, memory in cases: + for segments, memory, message in cases: container = bytearray(build_container(segments, [(0x100800, 0x3000, 0x400)], {0x3000: (b"load data\0" * 128)[:0x400]}, program_offset=PAGE)) if memory is not None: struct.pack_into("