Files
chimera-gfx-Public/docs/runtime/phase-0.9c-startup-exit-feasibility.md
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

290 lines
16 KiB
Markdown

# 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.