mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-27 00:28:14 +00:00
Merge pull request #10560 from Bram910/fix/audit-gnu-hash-chain-walks
fix(tools): avoid repeated GNU hash chain walks
This commit is contained in:
2 files changed
+64
-4
No files matched your search
@@ -127,16 +127,18 @@ def elf_program_dynamic_symbols(path, data):
|
||||
bucket_data = take(data, bucket_offset, buckets * 4, path, "GNU hash buckets")
|
||||
chain_offset = bucket_offset + buckets * 4
|
||||
maximum = first_symbol - 1
|
||||
visited_chain_entries = set()
|
||||
for index in struct.unpack(f"<{buckets}I", bucket_data) if buckets else ():
|
||||
if index == 0:
|
||||
continue
|
||||
if index < first_symbol:
|
||||
raise AuditError(f"{path}: GNU hash bucket index {index} precedes symbol offset {first_symbol}")
|
||||
symbol_index = index
|
||||
while True:
|
||||
while symbol_index not in visited_chain_entries:
|
||||
chain_address = dynamic[0x6ffffef5] + (chain_offset - hash_offset) + (symbol_index - first_symbol) * 4
|
||||
chain_file_offset = file_offset(chain_address, 4, "GNU hash chain")
|
||||
chain_value = unpack("<I", data, chain_file_offset, path, "GNU hash chain entry")[0]
|
||||
visited_chain_entries.add(symbol_index)
|
||||
maximum = max(maximum, symbol_index)
|
||||
if chain_value & 1:
|
||||
break
|
||||
|
||||
@@ -56,7 +56,7 @@ def build_elf(symbols, needed=()):
|
||||
return bytes(image)
|
||||
|
||||
|
||||
def build_sectionless_elf(symbols, needed=(), gnu_hash=False):
|
||||
def build_sectionless_elf(symbols, needed=(), gnu_hash=False, gnu_buckets=(1,)):
|
||||
strings = b"\0"
|
||||
entries = [struct.pack("<IBBHQQ", 0, 0, 0, 0, 0, 0)]
|
||||
for name, index, info in symbols:
|
||||
@@ -73,8 +73,9 @@ def build_sectionless_elf(symbols, needed=(), gnu_hash=False):
|
||||
symbol_table = b"".join(entries)
|
||||
hash_offset = (symbols_offset + len(symbol_table) + 3) & ~3
|
||||
if gnu_hash:
|
||||
hash_table = struct.pack("<IIII", 1, 1, 1, 0) + bytes(8)
|
||||
hash_table += struct.pack("<I", 1) + b"".join(struct.pack("<I", 1 if index == len(entries) - 1 else 0) for index in range(1, len(entries)))
|
||||
hash_table = struct.pack("<IIII", len(gnu_buckets), 1, 1, 0) + bytes(8)
|
||||
hash_table += b"".join(struct.pack("<I", index) for index in gnu_buckets)
|
||||
hash_table += b"".join(struct.pack("<I", 1 if index == len(entries) - 1 else 0) for index in range(1, len(entries)))
|
||||
hash_tag = 0x6ffffef5
|
||||
else:
|
||||
hash_table = struct.pack("<II", 1, len(entries)) + struct.pack("<I", 0) + bytes(4 * len(entries))
|
||||
@@ -100,6 +101,21 @@ def build_sectionless_elf(symbols, needed=(), gnu_hash=False):
|
||||
return bytes(image)
|
||||
|
||||
|
||||
def gnu_hash_chain_offset(image):
|
||||
header = struct.unpack_from("<16sHHIQQQIHHHHHH", image)
|
||||
segments = [struct.unpack_from("<IIQQQQQQ", image, header[5] + index * header[9])
|
||||
for index in range(header[10])]
|
||||
dynamic = next(segment for segment in segments if segment[0] == 2)
|
||||
tags = dict(struct.unpack_from("<qQ", image, position)
|
||||
for position in range(dynamic[2], dynamic[2] + dynamic[5], 16))
|
||||
address = tags[0x6ffffef5]
|
||||
load = next(segment for segment in segments
|
||||
if segment[0] == 1 and segment[3] <= address < segment[3] + segment[5])
|
||||
hash_offset = address - load[3] + load[2]
|
||||
bucket_count, _, bloom_count, _ = struct.unpack_from("<IIII", image, hash_offset)
|
||||
return hash_offset + struct.calcsize("<IIII") + bloom_count * struct.calcsize("<Q") + bucket_count * struct.calcsize("<I")
|
||||
|
||||
|
||||
def exporting(*names):
|
||||
return build_elf([(name, 1, GLOBAL_FUNCTION) for name in names])
|
||||
|
||||
@@ -1129,6 +1145,48 @@ class ImportAuditTests(unittest.TestCase):
|
||||
self.assertEqual(import_audit.elf_program_dynamic_symbols(Path("module.prx"), image),
|
||||
({"AAAAAAAAAAA": False}, {"BBBBBBBBBBB"}, {"libSceA.prx"}))
|
||||
|
||||
def test_sectionless_elf_gnu_hash_reads_each_chain_entry_once(self):
|
||||
symbols = [(f"symbol{index}", int(index != 0), GLOBAL_FUNCTION) for index in range(32)]
|
||||
expected = ({"symbol0": False}, {name for name, section, _ in symbols if section}, {"libSceA.prx"})
|
||||
for buckets, terminator in (((1,) * 32, None), (tuple(range(32, 0, -1)), None), ((17, 1, 0, 17), 16)):
|
||||
with self.subTest(buckets=buckets, terminator=terminator):
|
||||
image = bytearray(build_sectionless_elf(symbols, ["libSceA.prx"], gnu_hash=True, gnu_buckets=buckets))
|
||||
chain_offset = gnu_hash_chain_offset(image)
|
||||
if terminator is not None:
|
||||
struct.pack_into("<I", image, chain_offset + (terminator - 1) * 4, 1)
|
||||
with patch.object(import_audit, "unpack", wraps=import_audit.unpack) as unpack:
|
||||
self.assertEqual(import_audit.elf_program_dynamic_symbols(Path("module.prx"), image), expected)
|
||||
chain_reads = [call.args[2] for call in unpack.call_args_list
|
||||
if call.args[0] == "<I" and chain_offset <= call.args[2] < chain_offset + len(symbols) * 4]
|
||||
self.assertEqual(len(chain_reads), len(symbols))
|
||||
self.assertEqual(set(chain_reads), set(range(chain_offset, chain_offset + len(symbols) * 4, 4)))
|
||||
|
||||
def test_sectionless_elf_gnu_hash_empty_buckets(self):
|
||||
for buckets in ((), (0,), (0, 0, 0)):
|
||||
with self.subTest(buckets=buckets):
|
||||
image = build_sectionless_elf([], gnu_hash=True, gnu_buckets=buckets)
|
||||
chain_offset = gnu_hash_chain_offset(image)
|
||||
with patch.object(import_audit, "unpack", wraps=import_audit.unpack) as unpack:
|
||||
self.assertEqual(import_audit.elf_program_dynamic_symbols(Path("module.prx"), image), ({}, set(), set()))
|
||||
self.assertFalse(any(call.args[0] == "<I" and chain_offset <= call.args[2] < len(image)
|
||||
for call in unpack.call_args_list))
|
||||
|
||||
def test_sectionless_elf_gnu_hash_requires_file_backed_chains(self):
|
||||
symbols = [(f"symbol{index}", 1, GLOBAL_FUNCTION) for index in range(4)]
|
||||
for buckets in ((1, 1), (3, 1, 2)):
|
||||
for terminated in (False, True):
|
||||
image = bytearray(build_sectionless_elf(symbols, gnu_hash=True, gnu_buckets=buckets))
|
||||
chain_offset = gnu_hash_chain_offset(image)
|
||||
chain_end = chain_offset + len(symbols) * 4
|
||||
if not terminated:
|
||||
struct.pack_into("<I", image, chain_end - 4, 0)
|
||||
file_size = chain_end - int(terminated)
|
||||
struct.pack_into("<QQ", image, 64 + 32, file_size, file_size)
|
||||
for reader in (import_audit.elf_exports, import_audit.elf_imports):
|
||||
with self.subTest(buckets=buckets, terminated=terminated, reader=reader.__name__):
|
||||
with self.assertRaisesRegex(import_audit.AuditError, "GNU hash chain.*outside file-backed"):
|
||||
reader(Path("unmapped-chain.prx"), image)
|
||||
|
||||
def test_sectionless_hash_metadata_must_be_file_backed(self):
|
||||
for gnu_hash in (False, True):
|
||||
image = bytearray(build_sectionless_elf([("AAAAAAAAAAA", 1, GLOBAL_FUNCTION)], gnu_hash=gnu_hash))
|
||||
|
||||
Reference in new issue
Block a user