Files
chimera-gfx-Public/docs/runtime/loader-contract.md
T
Chimera GFX release export a6037502d7
phase0-ci / build-and-audit (push) Successful in 2m14s
Publish Chimera GFX source
2026-09-03 03:27:14 +02:00

10 KiB

PS5 payload loader contract audit

Audit date: 2026-07-17. Scope: pinned public PS5 Payload SDK v0.41 at d2e2e585740362976a39fdd5ccf390f199a7bc37, inspected offline. No PS5 was contacted and no ELF was built, transferred, or executed.

Available primary evidence

The SDK README names three external loader families: ps5-payload-dev/elfldr, cryonumb/elfloader through ps5-jar-loader, and shahrilnet/remote_lua_loader. None of those caller repositories is present in the pinned local evidence set. A git grep of every other local primary-source checkout found no payload_args_t or payloadout caller implementation. The only available contract is therefore the SDK callee side:

  • include/ps5/payload.h defines the six-field payload_args_t;
  • crt/crt.c defines int _start(payload_args_t *args) and labels it as the entry invoked by the ELF loader;
  • crt1.o disassembly shows the first argument consumed from RDI and payloadout read at offset 0x28;
  • the SDK linker script defines image, BSS, constructor, destructor, and dynamic-section boundaries.

These facts prove the callee layout. They do not prove how a particular loader constructs the argument, stack, return address, or post-return state.

Argument layout

For the audited x86-64 target the public fields are six eight-byte values in this order:

Offset Field Callee use
0x00 sys_dynlib_dlsym SDK syscall/symbol bootstrap
0x08 rwpipe existing kernel read/write transport descriptors
0x10 rwpair existing kernel read/write transport descriptors
0x18 kpipe_addr existing kernel pipe address
0x20 kdata_base_addr existing kernel data-base address
0x28 payloadout result location written by the SDK CRT

The 48-byte layout is SAFE as a source and disassembly fact. The lifetime, writability, ownership, nullability, and post-return consumption of every pointer are UNPROVEN because the caller is absent.

Entry ABI and stack

The compiled SDK _start has a normal x86-64 function prologue, consumes the argument from RDI, makes further calls without dynamically realigning RSP, and ends one path with ret. This establishes what the callee expects, not what the loader guarantees. In particular, the following remain UNPROVEN:

  • the exact stack alignment at entry;
  • whether a valid return address is present;
  • which registers or floating-point state the loader expects preserved;
  • whether the loader calls the entry as an ordinary function or transfers control by another mechanism;
  • whether the memory behind payload_args_t remains valid until return.

BSS and image initialization

The SDK linker script provides __bss_start and __bss_end. The stock _start explicitly zeros that range byte-by-byte before accessing its own BSS state. The loop itself is freestanding and has no import, so a comparable loop is technically possible. It is not evidence that the loader maps every segment with the permissions, alignment, relocation state, or zero-fill behavior a new artifact would require. Those caller/mapper properties are UNPROVEN.

Return, exit, crash, and cleanup

The stock CRT does not define one universal return rule. Its payload_terminate() first tries to detect a hijacked process with kernel_dynlib_dlsym(-1, 0x2001, "sceKernelDlsym"). In that branch it returns from _start. Otherwise it resolves and invokes exit; if that fails it executes a trap. This heuristic itself depends on the syscall/kernel/rtld stack that Phase 0.5 forbids.

Consequently, a custom _start that simply executes ret cannot be claimed safe from the SDK callee alone. No local primary evidence proves:

  • who consumes the integer return value;
  • whether return resumes a hijacked process or exits a dedicated process;
  • which mappings, descriptors, credentials, or signal state are restored;
  • what happens after an invalid argument, fault, trap, or partial BSS clear;
  • whether any cleanup exists after a crash.

All five are UNPROVEN and are build blockers.

Loader state versus artifact-caused state

The argument fields show that kernel read/write descriptors and kernel addresses already exist when the SDK entry is called. That is a fact about the incoming state, not proof of how the loader created it. The exact loader may have changed credentials, syscall bounds, mappings, modules, or other process state before entry; the available callee source cannot distinguish those possibilities.

The stock SDK artifact then independently causes additional writes through __patch_init; those writes are attributable to crt1.o and are UNSAFE. No Phase-0.5 candidate artifact exists, so it caused no new state changes.

Contract decision

The public callee contract is insufficient for a safe-return artifact. Decision: BLOCKED. The minimal missing evidence is the exact loader used for firmware 9.60 at a pinned public commit, including its entry call site, argument ownership, return path, pre-entry changes, cleanup, and crash path.

Machine evidence: manifests/runtime/phase-0.5-startup-audit.json.

Phase 0.6 exact-loader addendum

The Phase-0.5 conclusion above is preserved as the evidence available at that time. Phase 0.6 identified the installed loader exactly through the read-only Payload Manager inventory and a byte-for-byte public release match:

  • Payload Manager v0.3.1, commit cfbc70f30f419b09bf2b52283f7409e2d3117ee1, SHA-256 518740adbacccb9094fadb07dd424c53ee290f38306449ccc9d6957fdf813c0b;
  • ps5-payload-dev/elfldr v0.23, commit 699e8bcff03e91e8d6ca6eba281af25c5a58d8c2, SHA-256 092d16ee0ede0c494947efd38d1a17bbd7cc4b022d3858ea898833c188c703e8.

The proposed itsPLK/ps5-elfldr v0.23.2 candidate is not installed: its release hash is 2c28f847c859d3126b0f089ee23b72b84a733548da222ceafd91ae2f3df8f933. The exact exploit/autoloader that originally starts the first-stage loader remains unidentified.

Proven entry setup

The exact v0.23 source creates a new process using rfork_thread(RFPROC | RFCFDG | RFMEM). The child enables tracing and executes /system/vsh/app/NPXS40112/eboot.bin (SceSpZeroConf). The parent stops at an INT3 placed 58 bytes after the mapped eboot entry, restores that byte, maps the payload, and then sets:

Register/state Exact loader operation Status
RSP writes observed RIP at RSP - 8, then subtracts 8 SAFE source fact
RIP payload ELF entry SAFE source fact
RDI loader-created payload_args_t page SAFE source fact
breakpoint byte original byte restored before injection RESTORED_BY_LOADER
breakpoint page permission changed to RWX and not restored PAYLOAD_PROCESS_LOCAL

This is a synthetic function-call frame. The public source does not establish the meaning of the observed trap RIP, the exact original instruction boundary, or the validity of resuming there after payload return. Therefore the continuation is still UNPROVEN.

Argument ownership and lifetime

elfldr_payload_args() maps one page into the child, creates two IPv6 sockets and a pipe, overlaps the sockets through the SDK kernel helper, and populates the six public fields. On successful detach the loader does not unmap that page or close those child descriptors. They are process-local, but cleanup depends on the child terminating. That termination and reaping path is not bounded or proven.

Return, exit, crash, and hang

The stock SDK v0.41 _start may return, resolve and call exit, or trap, depending on payload_terminate(). Which branch applies to this injected SceSpZeroConf child is not proven. Separately, elfldr:

  • has no payload wall-clock deadline after PT_DETACH;
  • has unbounded single-step loops in both pt_call() and pt_syscall();
  • ignores SIGCHLD in the service and documents no post-detach reaping contract;
  • kills the child on some preparation errors, but does not supervise normal runtime, return, crash, or hang.

These are UNBOUNDED_OR_UNKNOWN, not objections to the mere presence of temporary kernel/process writes. They block the requested controlled two-second lifecycle.

Existing loader state versus artifact-caused state

The first-stage loader enables QA flag bits and temporarily changes credentials before the candidate payload exists. Those are existing-loader effects. For each ptrace operation the service temporarily changes its authid and normally restores it. The child receives process-local credentials, mappings, sockets, pipes, executable permissions, and SDK __patch_init changes. The artifact would cause SDK startup only after entry; it would not cause the loader bootstrap.

The separation does not make the chain ready: UID restoration is absent, authid restoration has an unrecovered failure path, and child lifetime is unbounded. Full evidence is in manifests/runtime/phase-0.6-loader-runtime-audit.json.

Phase 0.7 hardened loader contract

Phase 0.7 does not depend on safely returning from main. The lifecycle probe calls _exit after exactly one notification, so the unresolved SDK payload_terminate return/exit/trap branch is outside the reachable application path.

The hardened elfldr preserves the public payload_args_t * entry convention and adds a controlled versioned transport header with exact artifact ID, firmware 9.60, size, SHA-256, 2000 ms timeout, and no-retry fields. Both sender and receiver validate the metadata; the receiver hashes the actual received bytes and rejects the permanent denylist.

The loader's process mutations are exact and bounded:

  • the initial QA flag change is an existing volatile bootstrap effect;
  • ptrace service auth-ID and all child UID/jail/root/caps/auth fields are backed up, restored, and checked;
  • breakpoint byte and page protection are restored;
  • single-step loops have independent time and step limits;
  • every incomplete launch path terminates and reaps the child;
  • the detached success path is replaced with a two-second supervised wait, bounded termination escalation, and reap;
  • an auth restoration failure permanently disables further ptrace work and stops the service.

This is sufficient for the offline READY_FOR_HARDENED_RUNTIME_DEPLOYMENT decision. Loader behavior on firmware 9.60 remains UNPROVEN until separately authorized installation and execution produce on-device evidence.