290 lines
16 KiB
Markdown
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.
|