This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Synthetic tests for the Phase-1.0AH dynamic/relocation contract."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
sys.path.insert(0, str(ROOT / "tools"))
|
||||
|
||||
from phase10ag_bounded_elf import * # noqa: E402,F403
|
||||
from phase10ah_dynamic_contract import * # noqa: E402,F403
|
||||
|
||||
|
||||
NEEDED = ("libSceLibcInternal.sprx", "libkernel_web.sprx")
|
||||
|
||||
|
||||
def make_dynamic(relocations=None, needed=NEEDED, dynamic_terminated=True) -> bytes:
|
||||
strings = b"\0" + b"".join(name.encode("ascii") + b"\0" for name in needed)
|
||||
offsets = []
|
||||
position = 1
|
||||
for name in needed:
|
||||
offsets.append(position)
|
||||
position += len(name) + 1
|
||||
dynamic = b"".join(DYNAMIC_ENTRY.pack(DT_NEEDED, offset) for offset in offsets)
|
||||
if dynamic_terminated:
|
||||
dynamic += DYNAMIC_ENTRY.pack(DT_NULL, 0)
|
||||
if relocations is None:
|
||||
relocations = [(0x2100, R_X86_64_RELATIVE, 0, 0x100),
|
||||
(0x2108, R_X86_64_GLOB_DAT, 1, 0)]
|
||||
rela = b"".join(RELA_ENTRY.pack(target, symbol << 32 | kind, addend)
|
||||
for target, kind, symbol, addend in relocations)
|
||||
payload = bytearray(0x3400)
|
||||
ident = bytearray(16)
|
||||
ident[:7] = b"\x7fELF\x02\x01\x01"
|
||||
ELF_HEADER.pack_into(payload, 0, bytes(ident), ET_DYN, EM_X86_64, 1, 0x100,
|
||||
64, 0x3000, 0, 64, 56, 2, 64, 4, 0)
|
||||
PROGRAM_HEADER.pack_into(payload, 64, PT_LOAD, PF_R | PF_X, 0, 0, 0,
|
||||
0x1000, 0x1000, 0x1000)
|
||||
PROGRAM_HEADER.pack_into(payload, 120, PT_LOAD, PF_R | PF_W, 0x1000,
|
||||
0x2000, 0, 0x1000, 0x2000, 0x1000)
|
||||
payload[0x1800:0x1800 + len(strings)] = strings
|
||||
payload[0x1900:0x1900 + len(dynamic)] = dynamic
|
||||
payload[0x1a00:0x1a00 + len(rela)] = rela
|
||||
sections = [
|
||||
(0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
(0, SHT_STRTAB, 0, 0, 0x1800, len(strings), 0, 0, 1, 0),
|
||||
(0, SHT_DYNAMIC, 0, 0, 0x1900, len(dynamic), 1, 0, 8, 16),
|
||||
(0, SHT_RELA, 0, 0, 0x1a00, len(rela), 0, 0, 8, 24),
|
||||
]
|
||||
for index, section in enumerate(sections):
|
||||
SECTION_HEADER.pack_into(payload, 0x3000 + index * 64, *section)
|
||||
return bytes(payload)
|
||||
|
||||
|
||||
def assess(payload: bytes, needed=NEEDED):
|
||||
return assess_dynamic(payload, hashlib.sha256(payload).hexdigest(), needed)
|
||||
|
||||
|
||||
class DynamicContractTests(unittest.TestCase):
|
||||
def test_valid_split(self) -> None:
|
||||
result = assess(make_dynamic())
|
||||
self.assertEqual(result.relative_count, 1)
|
||||
self.assertEqual(result.glob_dat_count, 1)
|
||||
self.assertEqual(result.loader_applied_types, (R_X86_64_RELATIVE,))
|
||||
self.assertEqual(result.crt_applied_types, (R_X86_64_GLOB_DAT,))
|
||||
self.assertFalse(result.target_mapping_performed)
|
||||
|
||||
def test_needed_inventory_is_exact_and_allowlisted(self) -> None:
|
||||
payload = make_dynamic()
|
||||
with self.assertRaises(DynamicContractError):
|
||||
assess(payload, tuple(reversed(NEEDED)))
|
||||
with self.assertRaises(DynamicContractError):
|
||||
assess(make_dynamic(needed=("evil.sprx",)), ("evil.sprx",))
|
||||
|
||||
def test_dynamic_table_must_terminate(self) -> None:
|
||||
with self.assertRaises(DynamicContractError):
|
||||
assess(make_dynamic(dynamic_terminated=False))
|
||||
|
||||
def test_unknown_relocation_type_is_rejected(self) -> None:
|
||||
with self.assertRaises(DynamicContractError):
|
||||
assess(make_dynamic([(0x2100, 7, 0, 0x100)]))
|
||||
|
||||
def test_targets_must_be_aligned_and_writable(self) -> None:
|
||||
for target in (0x100, 0x2101, 0x5000):
|
||||
with self.subTest(target=target), self.assertRaises(DynamicContractError):
|
||||
assess(make_dynamic([(target, R_X86_64_RELATIVE, 0, 0x100)]))
|
||||
|
||||
def test_relative_requires_zero_symbol_and_mapped_addend(self) -> None:
|
||||
for symbol, addend in ((1, 0x100), (0, -1), (0, 0x9000)):
|
||||
with self.subTest(), self.assertRaises(DynamicContractError):
|
||||
assess(make_dynamic([(0x2100, R_X86_64_RELATIVE, symbol, addend)]))
|
||||
|
||||
def test_glob_dat_requires_symbol_and_zero_addend(self) -> None:
|
||||
for symbol, addend in ((0, 0), (1, 1)):
|
||||
relocs = [(0x2100, R_X86_64_RELATIVE, 0, 0x100),
|
||||
(0x2108, R_X86_64_GLOB_DAT, symbol, addend)]
|
||||
with self.subTest(), self.assertRaises(DynamicContractError):
|
||||
assess(make_dynamic(relocs))
|
||||
|
||||
def test_relative_closure_is_required(self) -> None:
|
||||
with self.assertRaises(DynamicContractError):
|
||||
assess(make_dynamic([(0x2108, R_X86_64_GLOB_DAT, 1, 0)]))
|
||||
|
||||
def test_base_elf_hash_and_shape_remain_enforced(self) -> None:
|
||||
payload = make_dynamic()
|
||||
with self.assertRaises(DynamicContractError):
|
||||
assess_dynamic(payload, "0" * 64, NEEDED)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
Reference in New Issue
Block a user