From bc444a675d4f695c5bd8dc0f7b4eb25930d23993 Mon Sep 17 00:00:00 2001 From: Patrick Elsasser Date: Sat, 10 Oct 2026 10:22:58 +0200 Subject: [PATCH] fix(relinker): reject guest modules in SELF containers A bundled module whose file starts with a SELF container magic (4f 15 3d 1d or 54 14 f5 ee) was skipped like any other non-ELF file, so the relink succeeded without it and the module was missing at run time. The guest module scan now throws, naming the module, as ElfReader already does for a SELF executable. Fixes #2599 Co-Authored-By: Claude Opus 5.5 --- .../relinker/src/guest/GuestModuleBuilder.cpp | 6 +++++- .../tests/test_guest_module_directories.py | 14 ++++++++++++++ docs/user/USAGE.md | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp index b4069cb83..7746bdf9b 100644 --- a/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp +++ b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp @@ -70,7 +70,11 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path char magic[4]{}; stream.read(magic, 4); if (stream.bad()) throw Domain::RelinkerException("Cannot read guest candidate magic: " + path.string()); - return stream.gcount() == 4 && static_cast(magic[0]) == 0x7f && magic[1] == 'E' && magic[2] == 'L' && magic[3] == 'F'; + if (stream.gcount() != 4) return false; + const auto byte = [&](const std::size_t index) { return static_cast(magic[index]); }; + const bool self = (byte(0) == 0x4f && byte(1) == 0x15 && byte(2) == 0x3d && byte(3) == 0x1d) || (byte(0) == 0x54 && byte(1) == 0x14 && byte(2) == 0xf5 && byte(3) == 0xee); + if (self) throw Domain::RelinkerException("Guest module is a SELF container, not an ELF: " + path.string()); + return byte(0) == 0x7f && magic[1] == 'E' && magic[2] == 'L' && magic[3] == 'F'; }; for (const auto& directory : directories) { if (!std::filesystem::is_directory(directory)) throw Domain::RelinkerException("Guest module path is not a directory: " + directory.string()); diff --git a/core/relinker/relinker/tests/test_guest_module_directories.py b/core/relinker/relinker/tests/test_guest_module_directories.py index e01b5dce0..a6e003f5a 100644 --- a/core/relinker/relinker/tests/test_guest_module_directories.py +++ b/core/relinker/relinker/tests/test_guest_module_directories.py @@ -110,6 +110,20 @@ def main(): assert result.returncode == 2 and "not a directory" in result.stderr, result.stderr assert not output.exists(), output + for magic in (b"\x4f\x15\x3d\x1d", b"\x54\x14\xf5\xee"): + case = work / f"{windows}-self-{magic.hex()}" + for name in ("sce_module", "prx"): + (case / name).mkdir(parents=True) + (case / "sce_module" / "libc.prx").write_bytes(magic + bytes(0x1000)) + (case / "prx" / "provider.prx").write_bytes(module_with_symbol(True)) + result, output = convert(case, windows) + assert result.returncode == 2 and "Guest module is a SELF container, not an ELF" in result.stderr, result.stderr + assert str(case / "sce_module" / "libc.prx") in result.stderr, result.stderr + assert not output.exists() and not (case / "app0").exists(), output + result, output = convert(case, windows, ["--exclude-sce-module", "libc.prx"]) + assert result.returncode == 0 and output.exists(), result.stderr + assert {path.name for path in (case / "app0").rglob("*.guest.prx")} == {"provider.prx.guest.prx"} + case = work / f"{windows}-ambiguous" for name in ("sce_module", "sce_modules", "prx"): (case / name).mkdir(parents=True) diff --git a/docs/user/USAGE.md b/docs/user/USAGE.md index f640c074f..3566d0ffd 100644 --- a/docs/user/USAGE.md +++ b/docs/user/USAGE.md @@ -2,7 +2,7 @@ ## Input and conversion -Use a clean ELF executable. Place its bundled ELF modules in `sce_module/`, `sce_modules/`, or `prx/` beside the input executable. `prx/` can coexist with either `sce_module/` or `sce_modules/`. Both `sce_module/` and `sce_modules/` present, or all three absent, is an error. +Use a clean ELF executable. Place its bundled ELF modules in `sce_module/`, `sce_modules/`, or `prx/` beside the input executable. A bundled module in a SELF container is an error, as the executable is. `prx/` can coexist with either `sce_module/` or `sce_modules/`. Both `sce_module/` and `sce_modules/` present, or all three absent, is an error. ```text source/