mirror of
https://github.com/spoopyghosty0/frameport.git
synced 2026-10-19 14:01:42 +02:00
Transformers Beyond Reality (UE5, FMallocBinned3) reserves 76 GiB with one mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS) while libUnreal.so's static constructors run. Quest grants it; the Frame's kernel (overcommit mode 0) refuses a writable private mapping larger than RAM + swap, and Unreal's out-of-memory handler then dereferences its not yet created log: SIGSEGV fault addr 0x10 inside System.loadLibrary. The patch adds MAP_NORESERVE to every anonymous read/write mmap call site of the engine library (mov w3, #0x22 -> #0x4022 before a bl to mmap's PLT stub, with x0 = 0 and w2 = 3 in the window), suggested from analysis unreal_binned3 (ANALYSIS_VERSION 22 on main). Triage unreal-reserve-refused, PLAYBOOK row, tests. Headless on the dev Frame the game now gets past loading to its OpenXR session and first frames. ELF helpers find .dynsym/.dynstr/.dynamic by section type and link instead of by name: this game's libOVRPlugin.so has a scrambled section name table, so add_needed raised "produced NEEDED=[...]" and the build failed in frame.unreal_ovrp_entrypoints, and dyn_symbols returned nothing (every ovrp_* lookup then counted as missing). # Conflicts: # docs/PATCHES.md # src/frameport/analysis/detect.py
73 lines
3.2 KiB
Python
73 lines
3.2 KiB
Python
import struct
|
|
|
|
import pytest
|
|
|
|
from frameport.analysis import elf
|
|
from frameport.analysis.stubgen import build_stub_library
|
|
|
|
|
|
@pytest.mark.parametrize("abi", ["arm64-v8a", "armeabi-v7a"])
|
|
def test_stub_library(abi):
|
|
lib = build_stub_library(["ovr_B", "ovr_A", "ovrMessageType_ToString"], abi=abi)
|
|
assert elf.is_elf(lib)
|
|
assert elf.is_64bit(lib) == (abi == "arm64-v8a")
|
|
assert elf.soname(lib) == "libovrstubs.so"
|
|
assert elf.dyn_symbols(lib, True) == {"ovr_A", "ovr_B", "ovrMessageType_ToString"}
|
|
assert elf.needed(lib) == []
|
|
# 16 KiB page-aligned loads, dynamic inside the RW load
|
|
loads = elf.load_segments(lib)
|
|
assert all(off % 16384 == va % 16384 for va, off, _ in loads)
|
|
|
|
|
|
def _with_note(lib: bytes) -> bytes:
|
|
"""Turn the stub's PT_GNU_STACK into a PT_NOTE so add_needed can reuse it (the common case)."""
|
|
buf = bytearray(lib)
|
|
phoff = struct.unpack_from("<Q", buf, 32)[0]
|
|
phentsize, phnum = struct.unpack_from("<H", buf, 54)[0], struct.unpack_from("<H", buf, 56)[0]
|
|
for i in range(phnum):
|
|
if struct.unpack_from("<I", buf, phoff + i * phentsize)[0] == 0x6474E551:
|
|
struct.pack_into("<I", buf, phoff + i * phentsize, 4)
|
|
return bytes(buf)
|
|
|
|
|
|
@pytest.mark.parametrize("variant", ["reuse_note", "relocate_phdrs"])
|
|
def test_add_needed(variant):
|
|
base = build_stub_library(["ovr_X"], soname="libgame.so")
|
|
base = elf.add_needed(base, "liblog.so") # give it one NEEDED first
|
|
if variant == "reuse_note":
|
|
base = _with_note(base)
|
|
out = elf.add_needed(base, "libglshim.so")
|
|
assert elf.needed(out)[0] == "libglshim.so"
|
|
assert elf.needed(out)[1:] == elf.needed(base)
|
|
assert elf.dyn_symbols(out, True) == {"ovr_X"}
|
|
assert elf.soname(out) == "libgame.so"
|
|
assert out[: len(base)][64:] != b"" and out.startswith(base[:16])
|
|
loads = elf.load_segments(out)
|
|
assert [va for va, _, _ in loads] == sorted(va for va, _, _ in loads)
|
|
assert all(off % 16384 == va % 16384 for va, off, _ in loads)
|
|
# idempotent
|
|
assert elf.add_needed(out, "libglshim.so") == out
|
|
|
|
|
|
def _scramble_section_names(lib: bytes) -> bytes:
|
|
"""Rotate every section's sh_name by one, like a shipped libOVRPlugin.so whose names are shifted (GitHub #349:
|
|
.dynamic named "dding", .dynsym "text"); types and links stay right."""
|
|
buf = bytearray(lib)
|
|
shoff = struct.unpack_from("<Q", buf, 40)[0]
|
|
shentsize, shnum = struct.unpack_from("<H", buf, 58)[0], struct.unpack_from("<H", buf, 60)[0]
|
|
names = [struct.unpack_from("<I", buf, shoff + i * shentsize)[0] for i in range(1, shnum)]
|
|
for i, name in enumerate(names[1:] + names[:1], start=1):
|
|
struct.pack_into("<I", buf, shoff + i * shentsize, name)
|
|
return bytes(buf)
|
|
|
|
|
|
def test_scrambled_section_names():
|
|
base = elf.add_needed(build_stub_library(["ovr_X", "ovr_Y"], soname="libgame.so"), "liblog.so")
|
|
lib = _scramble_section_names(base)
|
|
assert elf.dynsym_section(elf._elf(lib)) is not None
|
|
assert elf.dyn_symbols(lib, True) == {"ovr_X", "ovr_Y"}
|
|
assert elf.find_symbols(lib, ["ovr_Y"]).keys() == {"ovr_Y"}
|
|
out = elf.add_needed(lib, "libfp_x.so") # raised "produced NEEDED=[...]" when sections were found by name
|
|
assert elf.needed(out) == ["libfp_x.so", "liblog.so"]
|
|
assert elf.dyn_symbols(out, True) == {"ovr_X", "ovr_Y"}
|