# 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: ```text 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: ```text _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_terminate` deliberately traps if its resolved `exit` path returns or cannot be used (`crt.c:162-170`). - The controlled watchdog treats any reaped child as completion but discards the `waitpid` status (`ps5_controlled.c:36-59`). - If the child remains alive, the watchdog uses `SIGTERM`, then `SIGKILL`, and reaps it (`controlled_runtime.c:223-250`). - The connection handler emits the same fixed success text after `payload_spawn` returns 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: 1. kernelwrite-free entry with usable observation dependencies; 2. correct stack and register ABI; 3. deterministic BSS/relocation/TLS closure; 4. normal return or process exit with exact status semantics; 5. cleanup of mappings, descriptors, stack, loader and manager state; 6. a safe error path; 7. a safe deadline path that does not rely on kill; and 8. 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.