This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
# SDK v0.41 startup call graph
|
||||
|
||||
This is an offline source, relocation, and disassembly audit of the installed
|
||||
SDK v0.41 `target/lib/crt1.o`. It describes the unsafe stock startup, not a new
|
||||
Chimera GFX artifact. The complete machine-readable reachable graph contains
|
||||
31 functions and 182 call or tail-call edges in
|
||||
`manifests/runtime/phase-0.5-startup-audit.json`.
|
||||
|
||||
## Transitive startup objects
|
||||
|
||||
`crt/Makefile` compiles and partially links exactly these objects into one
|
||||
relocatable `crt1.o` with `prospero-lld -r`:
|
||||
|
||||
| Object | Public source | Role |
|
||||
|---|---|---|
|
||||
| `crt.o` | `crt/crt.c` | entry, BSS, init/run/terminate |
|
||||
| `syscall.o` | `crt/syscall.c` | syscall bootstrap |
|
||||
| `klog.o` | `crt/klog.c` | kernel log helpers |
|
||||
| `nid.o` | `crt/nid.c` | symbol-name encoding |
|
||||
| `kernel.o` | `crt/kernel.c` | kernel transport and process helpers |
|
||||
| `rtld.o` | `crt/rtld.c` | runtime-loader core |
|
||||
| `rtld_so.o` | `crt/rtld_so.c` | shared-object mapping and relocation |
|
||||
| `rtld_sprx.o` | `crt/rtld_sprx.c` | Sce module load/start/stop/unload |
|
||||
| `rtld_payload.o` | `crt/rtld_payload.c` | payload relocation and arrays |
|
||||
| `rtld_dlfcn.o` | `crt/rtld_dlfcn.c` | `dlopen`/`dlsym` facade |
|
||||
| `mdbg.o` | `crt/mdbg.c` | memory-debug transport helpers |
|
||||
| `patch.o` | `crt/patch.c` | credentials and syscall-bound patches |
|
||||
|
||||
The files named `crti.o`, `crtn.o`, `crtbegin.o`, `crtend.o`, `crtbeginS.o`,
|
||||
and `crtendS.o` are empty `ar` archives at the installed release. The audit
|
||||
enumerates them rather than assuming their filename implies contents.
|
||||
|
||||
## Reachable startup paths
|
||||
|
||||
The important conservative paths from source, relocations, and disassembly are:
|
||||
|
||||
```text
|
||||
_start
|
||||
|- zero [__bss_start, __bss_end)
|
||||
|- __crt_syscall_init
|
||||
|- __kernel_init
|
||||
| `- kernel_copyout (reachable in compiled object)
|
||||
|- __klog_init
|
||||
|- kernel_dynlib_dlsym -> set __isthreaded = 1
|
||||
|- __patch_init UNSAFE
|
||||
| |- kernel_get_ucred_caps -> kernel_copyout
|
||||
| |- kernel_get_ucred_attrs -> kernel_copyout
|
||||
| |- kernel_set_ucred_caps -> kernel_copyin KERNEL WRITE
|
||||
| |- kernel_set_ucred_attrs -> kernel_copyin KERNEL WRITE
|
||||
| `- kernel_copyin at process offsets 0xf0/0xf8 KERNEL WRITE
|
||||
|- __rtld_init UNSAFE
|
||||
| |- __rtld_sprx_init -> sceKernelLoadStartModule
|
||||
| |- __rtld_so_init
|
||||
| |- __rtld_payload_init
|
||||
| `- __rtld_dlfcn_init
|
||||
|- __rtld_payload_new/open/init
|
||||
| `- constructors
|
||||
|- main
|
||||
|- __rtld_lib_fini/close/destroy
|
||||
| `- destructors
|
||||
`- payload_terminate
|
||||
|- ret for the CRT's detected hijacked-process case
|
||||
|- resolved exit for another case
|
||||
`- trap when exit cannot be resolved
|
||||
```
|
||||
|
||||
Indirect calls are retained as `INDIRECT:<operand>` in the JSON graph rather
|
||||
than guessed. A relocation to the `sceKernelLoadStartModule` function-pointer
|
||||
slot is retained explicitly. This makes the graph conservative without
|
||||
inventing a firmware ABI.
|
||||
|
||||
## Linked but not startup-reachable
|
||||
|
||||
The monolithic `crt1.o` also contains `__dlopen`, `__dlsym`,
|
||||
`kernel_mprotect`, `kernel_overlap_sockets`, and
|
||||
`kernel_set_vmem_protection`, although the disassembly graph did not find them
|
||||
reachable from the stock `_start`. Their presence demonstrates why an import
|
||||
inventory alone would be insufficient: prohibited-capability code can be
|
||||
statically linked without appearing as an undefined symbol.
|
||||
|
||||
## Driver and linker behavior
|
||||
|
||||
The `prospero-clang` wrapper normally adds `crt1.o`, `libc`, `libkernel_web`,
|
||||
`libSceLibcInternal`, and `libSceNet`. A no-output `-###` trace with both
|
||||
`-nostartfiles` and `-nodefaultlibs` adds none of them. The linker wrapper still
|
||||
selects the public `elf_x86_64.x` script, PIE mode, an ELF x86-64 emulation,
|
||||
0x4000 maximum page size, emulated TLS support, and GNU hashing unless the
|
||||
caller overrides applicable options.
|
||||
|
||||
The linker script retains preinit/init/fini arrays and a dynamic segment. It
|
||||
also declares the text load segment with flags `0x7`; that is a source fact,
|
||||
not a claim about final loader-enforced permissions.
|
||||
|
||||
## Why there is no custom linker map
|
||||
|
||||
The loader return contract failed before the build gate. In accordance with
|
||||
the task rule, no custom `_start` source or PS5 ELF was created. Therefore a
|
||||
custom linker map, imports, `DT_NEEDED`, relocations, arrays, TLS inventory,
|
||||
double-build hash, and artifact disassembly are all explicitly recorded as
|
||||
`NOT_PERFORMED_BLOCKED_BEFORE_BUILD`, not silently treated as passing.
|
||||
|
||||
## Phase 0.6 exact runtime chain
|
||||
|
||||
This addendum is the reviewed source call graph for the installed chain. No new
|
||||
probe artifact exists, so there is no probe disassembly call graph to report.
|
||||
|
||||
```text
|
||||
Payload Manager /loadpayload:<path>
|
||||
`- ps5_launch_elf(path)
|
||||
|- open(path, O_RDONLY)
|
||||
|- connect(127.0.0.1:9021)
|
||||
`- send(ELF bytes)
|
||||
|
||||
elfldr serve_elfldr
|
||||
`- elfldr_spawn
|
||||
|- rfork_thread
|
||||
| `- child: elfldr_rfork_entry
|
||||
| |- sys_budget_set(0)
|
||||
| |- open(/dev/deci_{stdin,stdout,stderr})
|
||||
| |- ptrace(PT_TRACE_ME)
|
||||
| `- execve(SceSpZeroConf)
|
||||
|- parent: pt_syscall(599)
|
||||
| `- pt_syscall -> unbounded pt_step loop
|
||||
|- elfldr_set_heap_size(-1)
|
||||
|- kernel_mprotect(eboot entry page, RWX)
|
||||
|- install INT3 -> continue -> wait -> restore byte
|
||||
`- elfldr_exec
|
||||
|- backup jaildir/rootdir/caps/authid
|
||||
|- elfldr_raise_privileges
|
||||
| `- set rootdir/jaildir/UID/caps
|
||||
|- elfldr_prepare_exec
|
||||
| |- elfldr_load
|
||||
| | |- mmap/copy/RELATIVE relocations
|
||||
| | `- per-segment mprotect + msync
|
||||
| |- elfldr_payload_args
|
||||
| | |- mmap page
|
||||
| | |- two IPv6 sockets + overlap
|
||||
| | `- pipe + six payload_args_t fields
|
||||
| `- [RSP-8]=old RIP; RIP=entry; RDI=args
|
||||
|- restore jaildir/rootdir/caps/authid (not UID)
|
||||
`- ptrace(PT_DETACH)
|
||||
|
||||
SDK v0.41 payload _start
|
||||
|- clear BSS
|
||||
|- initialize syscall/kernel/klog
|
||||
|- __patch_init
|
||||
|- initialize rtld and constructors
|
||||
|- main
|
||||
|- destructors and rtld cleanup
|
||||
`- payload_terminate
|
||||
`- return OR exit OR trap (exact child branch unproven)
|
||||
```
|
||||
|
||||
The exact elfldr release binary is stripped, so the source graph is bound to
|
||||
the release by source commit and release-asset SHA-256. Static binary evidence
|
||||
records entry `0x4700`, 164 relocations, 24 undefined dynamic symbols, three
|
||||
`DT_NEEDED` modules, zero-byte init/fini arrays, no TLS, and hashes of the
|
||||
complete readelf report and disassembly in
|
||||
`manifests/runtime/phase-0.6-loader-runtime-audit.json`.
|
||||
|
||||
## Phase 0.7 hardened callgraph addendum
|
||||
|
||||
Phase 0.7 uses new hardened elfldr and controlled Payload Manager binaries;
|
||||
the graph above remains the historical stock Phase-0.6 chain.
|
||||
|
||||
The lifecycle source-level action is deliberately narrow:
|
||||
|
||||
```text
|
||||
SDK _start
|
||||
|- BSS and stock SDK v0.41 runtime initialization
|
||||
|- __patch_init
|
||||
|- main
|
||||
| |- sceKernelSendNotificationRequest
|
||||
| `- _exit
|
||||
`- payload_terminate [not reached from main]
|
||||
```
|
||||
|
||||
The hardened loader retains its required first-stage and ptrace operations,
|
||||
but each single-step operation now has both a one-second monotonic deadline
|
||||
and a 65,536-step ceiling. Cleanup tracks the child, breakpoint, mappings, and
|
||||
descriptors in one state. The success and failure paths restore all five
|
||||
credential fields and verify every restoration. The runtime supervisor waits
|
||||
at most 2000 ms, then uses a bounded SIGTERM/SIGKILL/reap sequence.
|
||||
|
||||
The controlled manager opens, hashes, rewinds, and streams one no-follow file
|
||||
descriptor. The hardened receiver independently rehashes the received bytes
|
||||
and checks the exact versioned header and permanent denylist.
|
||||
|
||||
The committed machine audit contains every extracted call edge, not a sample:
|
||||
|
||||
- lifecycle: 499 edges;
|
||||
- hardened elfldr: 1032 edges;
|
||||
- controlled manager: 478 edges.
|
||||
|
||||
Full disassembly, normalized linker maps, symbol/relocation/section reports,
|
||||
and text callgraphs are in the ignored `outputs/phase07/audit/` package. Their
|
||||
hashes and the complete edge arrays are committed in
|
||||
`manifests/runtime/phase-0.7-offline-audit.json`.
|
||||
Reference in New Issue
Block a user