194 lines
6.7 KiB
Python
194 lines
6.7 KiB
Python
#!/usr/bin/env python3
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
"""Audit the pinned SDK startup/loader chain without executing a PS5 ELF."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
|
|
EXPECTED_SDK_COMMIT = "d2e2e585740362976a39fdd5ccf390f199a7bc37"
|
|
|
|
|
|
def require_in_order(text: str, snippets: tuple[str, ...], source: Path) -> None:
|
|
position = -1
|
|
for snippet in snippets:
|
|
position = text.find(snippet, position + 1)
|
|
if position < 0:
|
|
raise ValueError(f"{source}: missing or reordered evidence: {snippet}")
|
|
|
|
|
|
def read(source: Path, relative: str) -> str:
|
|
path = source / relative
|
|
return path.read_text(encoding="utf-8")
|
|
|
|
|
|
def git_head(source: Path) -> str:
|
|
result = subprocess.run(
|
|
["git", "-C", str(source), "rev-parse", "HEAD"],
|
|
check=True,
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
return result.stdout.strip()
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--sdk-source", type=Path, required=True)
|
|
parser.add_argument("--project-root", type=Path, required=True)
|
|
parser.add_argument("--output", type=Path, required=True)
|
|
args = parser.parse_args()
|
|
|
|
sdk = args.sdk_source.resolve(strict=True)
|
|
project = args.project_root.resolve(strict=True)
|
|
if git_head(sdk) != EXPECTED_SDK_COMMIT:
|
|
raise ValueError("SDK source checkout differs from the pinned v0.41 commit")
|
|
|
|
crt = read(sdk, "crt/crt.c")
|
|
patch = read(sdk, "crt/patch.c")
|
|
rtld = read(sdk, "crt/rtld.c")
|
|
dlfcn = read(sdk, "crt/rtld_dlfcn.c")
|
|
sprx = read(sdk, "crt/rtld_sprx.c")
|
|
kernel = read(sdk, "crt/kernel.c")
|
|
probe_main = read(project, "samples/capability_probe/main.c")
|
|
probe_platform = read(project, "src/backends/ps5/probe_platform.c")
|
|
|
|
require_in_order(
|
|
crt,
|
|
(
|
|
"__crt_syscall_init(args)",
|
|
"__kernel_init(args)",
|
|
"__klog_init()",
|
|
"__patch_init()",
|
|
"__rtld_init()",
|
|
"*payload_args->payloadout = main(argc, argv, environ)",
|
|
),
|
|
sdk / "crt/crt.c",
|
|
)
|
|
require_in_order(
|
|
patch,
|
|
(
|
|
"patch_kernel_ucred()",
|
|
"patch_syscall_permissions()",
|
|
),
|
|
sdk / "crt/patch.c",
|
|
)
|
|
for snippet in (
|
|
"kernel_set_ucred_caps(pid, caps)",
|
|
"kernel_set_ucred_attrs(pid, attrs)",
|
|
"kernel_copyin(&uaddr, kaddr + 0xf0, sizeof(uaddr))",
|
|
"kernel_copyin(&uaddr, kaddr + 0xf8, sizeof(uaddr))",
|
|
):
|
|
if snippet not in patch:
|
|
raise ValueError(f"crt/patch.c: missing expected write: {snippet}")
|
|
require_in_order(
|
|
rtld,
|
|
("__rtld_sprx_init()", "__rtld_dlfcn_init()"),
|
|
sdk / "crt/rtld.c",
|
|
)
|
|
for snippet in (
|
|
"sceKernelLoadStartModule(\"/system/common/lib/libSceSysmodule.sprx\"",
|
|
"sceKernelLoadStartModule(path, 0, 0, 0, 0, 0)",
|
|
"sceKernelStopUnloadModule(lib->handle, 0, 0, 0, 0, 0)",
|
|
):
|
|
if snippet not in sprx:
|
|
raise ValueError(f"crt/rtld_sprx.c: missing loader evidence: {snippet}")
|
|
require_in_order(
|
|
dlfcn,
|
|
("__rtld_lib_open(lib)", "__rtld_lib_init(lib, getargc(), getargv(), environ)"),
|
|
sdk / "crt/rtld_dlfcn.c",
|
|
)
|
|
require_in_order(
|
|
dlfcn,
|
|
("__rtld_lib_fini(lib)", "__rtld_lib_close(lib)", "__rtld_lib_destroy(lib)"),
|
|
sdk / "crt/rtld_dlfcn.c",
|
|
)
|
|
for snippet in ("int\nkernel_copyin", "int\nkernel_copyout"):
|
|
if snippet not in kernel:
|
|
raise ValueError(f"crt/kernel.c: missing kernel I/O primitive: {snippet}")
|
|
require_in_order(
|
|
probe_main,
|
|
(
|
|
"chimera_gfx_firmware_gate_allows",
|
|
"chimera_gfx_ps5_make_loader_ops",
|
|
"chimera_gfx_ps5_probe_symbols",
|
|
),
|
|
project / "samples/capability_probe/main.c",
|
|
)
|
|
for snippet in (
|
|
"dlopen(module_name, RTLD_LAZY | RTLD_LOCAL)",
|
|
"address = dlsym(loader->module, symbol_name)",
|
|
"address = NULL;",
|
|
"dlclose(loader->module)",
|
|
):
|
|
if snippet not in probe_platform:
|
|
raise ValueError(f"probe_platform.c: missing loader boundary: {snippet}")
|
|
|
|
document = {
|
|
"schema_version": 1,
|
|
"sdk": {
|
|
"release": "v0.41",
|
|
"commit": EXPECTED_SDK_COMMIT,
|
|
},
|
|
"execution_eligible_under_project_policy": False,
|
|
"blocking_side_effects_before_main": [
|
|
"__patch_init calls patch_kernel_ucred",
|
|
"patch_kernel_ucred writes process capability and attribute fields",
|
|
"__patch_init calls patch_syscall_permissions",
|
|
"patch_syscall_permissions writes the process syscall-address bounds",
|
|
],
|
|
"startup_calls_before_main": [
|
|
"__crt_syscall_init",
|
|
"__kernel_init",
|
|
"__klog_init",
|
|
"__patch_init",
|
|
"__rtld_init",
|
|
"__rtld_sprx_init",
|
|
"__rtld_dlfcn_init",
|
|
"payload constructors",
|
|
],
|
|
"project_requested_calls_after_firmware_gate": [
|
|
"fprintf",
|
|
"chimera_gfx_ps5_make_loader_ops",
|
|
"dlopen",
|
|
"dlerror",
|
|
"dlsym (21 bounded lookups; returned addresses discarded)",
|
|
"snprintf",
|
|
"dlclose",
|
|
],
|
|
"loader_side_effects": [
|
|
"SDK rtld startup may load and start libSceSysmodule.sprx",
|
|
"probe dlopen may load and start libSceGnmDriver.sprx",
|
|
"module open allocates user memory and copies symbol/string tables",
|
|
],
|
|
"cleanup_side_effects": [
|
|
"dlclose invokes rtld fini, close, and destroy paths",
|
|
"a newly loaded SPRX is stopped/unloaded with sceKernelStopUnloadModule",
|
|
"SDK sprx_init and sprx_fini are empty at the pinned commit",
|
|
"cleanup is not guaranteed after a hang, crash, or loader failure",
|
|
],
|
|
"conclusion": (
|
|
"Project code requests no GNM call, rendering, or GPU mutation, but the "
|
|
"linked SDK payload CRT performs prohibited kernel writes before main. "
|
|
"The resulting ELF must not be transferred or executed."
|
|
),
|
|
}
|
|
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|
args.output.write_text(
|
|
json.dumps(document, indent=2, sort_keys=True) + "\n", encoding="utf-8"
|
|
)
|
|
print("SDK runtime audit completed: execution blocked by pre-main kernel writes")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
raise SystemExit(main())
|
|
except (OSError, subprocess.CalledProcessError, ValueError) as error:
|
|
print(f"SDK runtime audit failed: {error}")
|
|
raise SystemExit(1) from error
|