16 KiB
Phase 0.9C startup and exit feasibility
Status: BLOCKED. This is a source-bound offline analysis. It does not add an observer, target source, PS5 object, ELF, linker map, installation package, lifecycle package, transfer path, or execution authority.
Evidence boundary
The exact reviewed sources are:
| Component | Commit | Relevant file identity |
|---|---|---|
| hardened elfldr | 197623058f509eddde18868dafcb92fdcac66464 |
elfldr.c SHA-256 9949f8e4037984d10f1f5aa498e4665593d1fac8a33614d7f2141349839bb803 |
| hardened elfldr controlled route | same | socksrv.c SHA-256 d642ced3e9b4a296dd15e355050ebe956f53a6dfdaa6ac10109cd067a3bba3d7 |
| hardened cleanup model | same | controlled_runtime.c SHA-256 10145f3bbb3b54e3d715b1667e45c6f12d3c7f52a04342ab104ec8dd49e384c9 |
| hardened PS5 cleanup adapter | same | ps5_controlled.c SHA-256 68717ef1cc31c483743c5af325c59e73a90f66e93b880b989e349a4ddc748772 |
| controlled Payload Manager | e23d94ff91233aa770e2342800c1467875bdef44 |
verified_launcher.c SHA-256 066100ca4917c7acc560e2e85666ca136cd7ccfd9094417377048f41106dd56e |
| controlled manager adapter | same | ps5_launcher.c SHA-256 29c1a5fd01784a59e88b3698940f120cb03020071bc2b7d74a1da1a51524ef59 |
| PS5 Payload SDK v0.41 | d2e2e585740362976a39fdd5ccf390f199a7bc37 |
crt/crt.c SHA-256 3875f4739ec40b33f1f4967a1acbb585a527d2c281a360153e1f69c8b945932a |
| SDK patch implementation | same | crt/patch.c SHA-256 4f76a677bba54f4641e1cf2755768c29afb7464a35b2f21e85db2aa2785eceac |
| SDK CRT object set | same | crt/Makefile SHA-256 6a62f777f32ab05cbe7bff81c00cfd6639ba8bf440aba611a40ff0b62732568b |
| SDK compiler wrapper | same | host/bin/prospero-clang SHA-256 0cf49ae43d6110a7606c0ee4d702fc4b5d5e1c3ae9a722945a48b80294e295ba |
| SDK linker script | same | host/elf_x86_64.x SHA-256 169b80d01da601ef96bbc584986608dec1d9c01397eae81eccd4e1a66b0a6c6a |
The public bases remain elfldr
699e8bcff03e91e8d6ca6eba281af25c5a58d8c2 and Payload Manager
cfbc70f30f419b09bf2b52283f7409e2d3117ee1. The lifecycle source remains
bound to commit fe08300339a13f899fb78ea404ada381a5cba87c; its unchanged
samples/lifecycle_probe/main.c is
1ae7df1fe921ccab2a252f77975d3d441ef7725e34535b024580c0d4a242d766.
None of these historical target sources was modified.
Classifications in this document mean:
PROVEN_SAFE: the cited source proves the narrow property stated;PROVEN_SIDE_EFFECTING: the cited source proves a state change or external operation, whether or not an earlier phase accepted it;UNPROVEN: evidence required for the claimed contract is absent;NOT_APPLICABLE: the path deliberately excludes the facility.
PROVEN_SAFE never means hardware-tested.
Loader-to-entry call graph
The controlled route has this source-proven graph:
controlled manager
|- open_verified
| |- open(O_RDONLY | O_NOFOLLOW | O_CLOEXEC)
| |- fstat -> bounded read/SHA-256 -> fstat
| `- lseek(fd, 0)
|- connect_loopback
|- send_all(control header)
|- send_all(exact ELF bytes)
`- close_socket
hardened elfldr on_connection
|- recv/validate controlled header
|- recv exact payload bytes -> independent SHA-256
|- payload_spawn(route=CONTROLLED)
| `- elfldr_spawn(stdio=-1)
| |- rfork_thread -> child execve(SceSpZeroConf)
| |- wait for exec/stop under finite preparation waits
| |- pt_syscall(process-needed-and-relocate)
| |- change child heap-size parameter
| |- make entry page RWX
| |- install INT3 -> continue -> stop
| |- restore byte and RX protection
| |- map payload PT_LOAD image
| |- apply R_X86_64_RELATIVE relocations
| |- map payload_args_t page
| |- create two IPv6 sockets, overlap them, and create a pipe
| |- back up child credentials
| |- raise child credentials
| |- write saved RIP to [RSP-8]
| |- set RSP=RSP-8, RIP=ELF entry, RDI=payload_args_t*
| |- restore all five credential fields
| `- ptrace detach
|- watchdog wait/reap
| `- on timeout: SIGTERM -> grace -> SIGKILL -> reap
`- write fixed loader-generated exit text
Sources: hardened elfldr socksrv.c:129-157,162-215,289-375;
elfldr.c:80-265,273-415,480-545,643-769;
controlled_runtime.c:60-92,95-143,146-220,223-250; and
ps5_controlled.c:26-59,132-220. The manager side is
verified_launcher.c:31-184, include/verified_launcher.h:38-42, and
ps5_launcher.c:18-69.
Entry contract classification
| Property | Classification | Evidence and consequence |
|---|---|---|
entry symbol comes from e_entry |
PROVEN_SAFE |
elfldr.c:143-265 returns base + e_entry. |
first argument in RDI |
PROVEN_SAFE |
elfldr.c:401-409 sets r_rdi to the loader-created argument page. |
| synthetic return address | PROVEN_SIDE_EFFECTING |
elfldr.c:401-407 writes the observed RIP at RSP-8 and moves RSP; the child stack is changed. |
| x86-64 stack alignment at entry | UNPROVEN |
the loader subtracts eight but never validates the trapped RSP alignment. |
| meaning and safety of saved RIP | UNPROVEN |
it is the RIP observed at the breakpoint; no reviewed contract proves the post-ret continuation. |
| preserved general/SIMD register set | UNPROVEN |
source changes RSP, RIP, and RDI but defines no caller/callee preservation contract for a custom entry. |
| argument layout | PROVEN_SAFE |
SDK include/ps5/payload.h:27-36 and elfldr elfldr.c:349-367 agree on six x86-64 fields. |
| argument ownership | PROVEN_SIDE_EFFECTING |
elfldr maps and populates the page in the child at elfldr.c:273-372. |
| argument lifetime after return or exit | UNPROVEN |
success clears the loader cleanup bitmap at elfldr.c:538-545; the mapping then relies on child lifetime/OS teardown. |
| image mapping | PROVEN_SIDE_EFFECTING |
elfldr allocates child mappings, copies bytes, changes protection, and calls msync at elfldr.c:143-265. |
| relocation support | PROVEN_SAFE only for R_X86_64_RELATIVE |
elfldr.c:208-222; no complete freestanding artifact exists whose relocations can be checked. |
| BSS zero-fill by loader | UNPROVEN and source-negative |
base_mirror comes from malloc; data_load copies only p_filesz, then the entire mirror is copied. No zero-fill is visible at elfldr.c:96-109,183-224. |
| TLS setup | UNPROVEN |
elfldr has no reviewed TLS setup in this path; no future observer layout exists. |
| constructors/destructors | NOT_APPLICABLE to a proposed freestanding entry |
they are a stock CRT facility and are forbidden for the proposed path. |
Normal SDK v0.41 startup
The exact normal graph is:
_start(payload_args_t *)
|- clear [__bss_start, __bss_end)
|- store payload_args
|- payload_init
| |- __crt_syscall_init
| |- __kernel_init
| |- __klog_init
| |- resolve __isthreaded and write 1
| |- __patch_init
| | |- get process/ucred state
| | |- write credential capability bytes
| | |- write credential attribute bytes
| | |- write syscall lower bound 0
| | `- write syscall upper bound -1
| `- __rtld_init
|- setjmp
|- payload_run
| |- resolve argc/argv/environ/progname
| |- create/open payload rtld object
| |- load DT_NEEDED dependencies and relocate
| |- run init array
| |- main
| |- run fini array
| `- close/destroy rtld object
`- payload_terminate
|- return 0 for the runtime's detected hijacked-process case
|- resolve and call exit(payloadout)
`- trap if exit resolution/call returns
Sources: SDK crt/crt.c:30-44,51-80,88-146,154-180,197-220;
crt/patch.c:27-100; crt/rtld_payload.c:51-60,203-260; and
crt/Makefile:19-26,50-54.
| Step | Classification | Reason |
|---|---|---|
| BSS clear | PROVEN_SIDE_EFFECTING |
writes every byte in the payload BSS; deterministic and process-local, but still a mutation. |
| global argument/setjmp state | PROVEN_SIDE_EFFECTING |
writes CRT BSS state. |
| syscall/kernel/klog bootstrap | PROVEN_SIDE_EFFECTING |
consumes loader-provided kernel transport and initializes global function/state. |
__isthreaded = 1 |
PROVEN_SIDE_EFFECTING |
writes resolved libc state at crt.c:65-69. |
__patch_init |
PROVEN_SIDE_EFFECTING and prohibited |
exact capability, attribute, and syscall-bound kernel writes occur before main. |
| rtld initialization | PROVEN_SIDE_EFFECTING |
resolves functions, allocates bookkeeping, and can load/start Sce modules. |
relocation and DT_NEEDED processing |
PROVEN_SIDE_EFFECTING |
writes relocations and opens dependencies. |
| constructors | PROVEN_SIDE_EFFECTING |
arbitrary linked constructor effects are permitted by this path. |
| heap use | PROVEN_SIDE_EFFECTING |
stock rtld resolves and uses allocation functions. |
main |
UNPROVEN for an observer |
no Phase-0.9C target implementation exists. |
| destructors and rtld close | PROVEN_SAFE only as attempted normal-path calls |
no proof covers partial initialization, longjmp, trap, crash, or module-internal cleanup. |
payload_terminate branch selection |
UNPROVEN |
the actual branch and its platform semantics are not bound to an observer execution. |
The normal SDK startup is therefore not kernelwrite-free and is unsuitable for Phase 0.9C. The fact that Phase 0.7 accepted its bounded, process-local effects for a different lifecycle profile does not satisfy this phase's stricter contract.
Error and abnormal paths
Initialization error
_start writes the error to payloadout and calls payload_terminate
(crt.c:207-210). A failure after credential capabilities were written but
before all patch writes finish has no CRT restoration path. A failure during
rtld initialization has no demonstrated full unwind in _start.
Classification: PROVEN_SIDE_EFFECTING for partial startup and UNPROVEN for
complete restoration and termination.
Runtime error
payload_run attempts close/destroy on some open/init/fini failures
(crt.c:116-146). These are source-proven calls, not proof of module, heap,
TLS, file-descriptor, or process cleanup on firmware 9.60.
Classification: PROVEN_SAFE for the presence of the attempted calls;
UNPROVEN for complete cleanup.
payload_exit
payload_exit writes payloadout and longjmps to _start
(crt.c:175-180). The longjmp leaves payload_run; _start proceeds directly
to payload_terminate, so the normal fini/close/destroy sequence is not on
that control path.
Classification: PROVEN_SIDE_EFFECTING; full cleanup is UNPROVEN.
Trap, crash, unexpected return, and timeout
payload_terminatedeliberately traps if its resolvedexitpath returns or cannot be used (crt.c:162-170).- The controlled watchdog treats any reaped child as completion but discards
the
waitpidstatus (ps5_controlled.c:36-59). - If the child remains alive, the watchdog uses
SIGTERM, thenSIGKILL, and reaps it (controlled_runtime.c:223-250). - The connection handler emits the same fixed success text after
payload_spawnreturns a nonnegative value (socksrv.c:361-365).
A trap/crash can therefore be indistinguishable from a normal exit to the
manager, and timeout termination is not a safe exit under the Phase-0.9C
rules. Classification: UNPROVEN for crash cleanup and status meaning;
PROVEN_SIDE_EFFECTING and inadmissible for signal termination.
Freestanding feasibility without target implementation
prospero-clang omits crt1.o for -nostartfiles and omits libc,
libkernel_web, libSceLibcInternal, and libSceNet for -nodefaultlibs
(host/bin/prospero-clang:25-46,69-84). This proves driver suppression only.
No custom entry source or target was assembled or linked.
| Required facility | Theoretical availability | Classification |
|---|---|---|
| entry address | ELF e_entry is transferred to RIP |
PROVEN_SAFE narrow loader fact |
| valid stack memory | an existing child stack is used | UNPROVEN size, alignment, guard, and post-return contract |
| small automatic buffers | mechanically possible if the stack contract holds | UNPROVEN |
| read-only constants | PT_LOAD bytes can be mapped and protected | PROVEN_SAFE only for loader mapping; relocation closure is artifact-dependent |
| BSS | loader does not prove zero-fill; a custom clear is mechanically possible | UNPROVEN until a reviewed entry and map exist |
| TLS | no freestanding TLS contract | UNPROVEN |
| constructors/destructors | excluded | NOT_APPLICABLE |
| heap | excluded | NOT_APPLICABLE |
| libc/stdio | excluded by design | NOT_APPLICABLE |
| raw syscalls | no callable observer syscall ABI is supplied by payload_args_t |
UNPROVEN |
| system calls from injected text | SDK patch widens permitted syscall address bounds | UNPROVEN without the prohibited patch |
| monotonic time | hardened loader has a clock, but exposes no clock callback to the payload | UNPROVEN in the entry |
filesystem open/read/fstat/close |
requires unresolved imports, a proven raw-syscall ABI, or another supplied callback | UNPROVEN |
| process/service/listener queries | no freestanding callable ABI is supplied | UNPROVEN |
normal ret |
mechanically reaches the saved RIP | UNPROVEN continuation and cleanup |
| direct process exit | would require a proven import/raw syscall and exact teardown contract | UNPROVEN |
| exit status | current waiter discards it | UNPROVEN delivery |
| output | current payloadout is inside the child mapping and never read by the controlled caller |
UNPROVEN |
The important closure result is negative: removing the CRT also removes the only reviewed syscall, kernel, rtld, import, time, and libc initialization machinery. The loader supplies a dynamic-symbol function pointer and kernel transport fields, but using them would reintroduce dynamic resolution or kernel-access dependencies that this phase excludes. No useful observation primitive is proven available to a zero-import freestanding entry.
Return, exit, and cleanup contract
| Resource/state | Normal ret |
process exit | error/crash | timeout |
|---|---|---|---|---|
| saved stack continuation | resumes the observed RIP; meaning unproven | not used | may be bypassed | bypassed |
| ELF and argument mappings | remain in child after detach | OS reclamation expected but not source-proven for this exact contract | unproven | child is killed/reaped, which is not safe exit |
| loader-created pipe/sockets | remain in child after detach | same unproven teardown dependency | unproven | same inadmissible kill path |
| observer local buffers | stack lifetime follows unproven continuation/process semantics | teardown semantics unproven | unproven | forcibly discarded |
| file descriptors opened by a future observer | no implementation or cleanup contract | no implementation or exact teardown proof | unproven | forcibly discarded |
| threads | a future observer forbids them; existing child/runtime state remains | no target observer exists | unproven | signals affect the whole child |
| credentials | loader restores five fields before detach | source-proven attempted and checked restore | pre-detach cleanup is tracked; post-detach crash remains dependent on process teardown | watchdog does not prove observer cleanup |
| breakpoint/page | restored before payload entry | already restored | preparation failures use cleanup | already restored |
| loader bookkeeping | returns from watchdog on reap | waitpid status is discarded |
status is ambiguous | returns timeout/failure |
| manager bookkeeping | send-only function closes its socket before a result | no result is received | indistinguishable | no result is received |
| exit status | no defined status | locally available to waitpid but discarded |
discarded | timeout result is loader-local |
| output availability | none | child mapping gone; no copy-out | none | none |
The current normal return path is especially unsuitable: ret resumes the
SceSpZeroConf continuation rather than proving process termination. The
controlled watchdog waits for process exit; if that continuation remains
alive, the only bounded endpoint is signal termination. The user explicitly
forbids treating kill, crash, timeout, or disconnect as a safe exit.
Decision
No reviewed path proves all of:
- kernelwrite-free entry with usable observation dependencies;
- correct stack and register ABI;
- deterministic BSS/relocation/TLS closure;
- normal return or process exit with exact status semantics;
- cleanup of mappings, descriptors, stack, loader and manager state;
- a safe error path;
- a safe deadline path that does not rely on kill; and
- result availability after cleanup.
Startup is therefore BLOCKED_STARTUP_ABI_UNPROVEN, exit and cleanup are
BLOCKED_EXIT_CLEANUP_UNPROVEN, and the combined Phase-0.9C decision cannot be
positive.