Files
spoopyghosty0--frameport/tests/test_elf.py
T
spoopyghosty0 d7810d28b7 Unreal Binned3 reservations with MAP_NORESERVE (frame.unreal_noreserve, GitHub #349)
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
2026-10-17 02:19:03 -04:00

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"}