269 lines
10 KiB
Markdown
269 lines
10 KiB
Markdown
# Phase 0.9C output-channel feasibility
|
||
|
||
Status: **HOST CONTRACT ONLY — NO CURRENT OUTPUT CHANNEL**.
|
||
|
||
This document evaluates the four requested architectures in order. It defines
|
||
a finite host protocol because D1 is conceptually coherent, but it does not
|
||
claim that hardened elfldr, the controlled Payload Manager, or any target
|
||
implements that protocol. No production runtime was changed.
|
||
|
||
## D1 — caller-owned bounded result buffer
|
||
|
||
### Current architecture
|
||
|
||
The current `payload_args_t.payloadout` is not a caller-owned observer result
|
||
buffer:
|
||
|
||
1. hardened elfldr maps the argument page **inside the child process**
|
||
(`elfldr.c:273-287`);
|
||
2. `payloadout` is `args_mapping + 0x300`
|
||
(`elfldr.c:349-367`);
|
||
3. after detach, elfldr clears its tracked cleanup bitmap and does not read
|
||
`payloadout` (`elfldr.c:538-545`);
|
||
4. on process exit, the controlled waiter discards the `waitpid` status
|
||
(`ps5_controlled.c:36-59`); and
|
||
5. the controlled connection writes only fixed loader text
|
||
(`socksrv.c:361-365`).
|
||
|
||
The current field is therefore child-owned mapped storage whose useful
|
||
post-exit lifetime and consumption are absent. It cannot be relabelled as D1.
|
||
|
||
### Future concept
|
||
|
||
A D1 implementation could be reviewed only if a future loader revision:
|
||
|
||
- allocates exactly 4096 bytes under caller ownership;
|
||
- zero-initializes that buffer;
|
||
- makes one bounded view available for one execution;
|
||
- retains ownership outside the observer process;
|
||
- binds it to a 128-bit execution nonce and 128-bit request ID;
|
||
- prevents concurrent or reused writers;
|
||
- obtains a proven normal return/exit indication;
|
||
- validates the observer body before accepting it;
|
||
- records cleanup outcome after the observer can no longer write;
|
||
- writes the final checksum and completion marker last;
|
||
- reads it once and rejects duplicate consumption; and
|
||
- destroys the transient object without filesystem, listener, log, or
|
||
persistent shared-memory state.
|
||
|
||
That design requires a loader ABI and implementation change in a later phase.
|
||
It also depends on the startup/exit contract that Phase 0.9C did not prove.
|
||
Classification:
|
||
`CONCEPT_FEASIBLE_REQUIRES_LOADER_CHANGE_AND_EXIT_PROOF`.
|
||
|
||
## D2 — existing request/response channel
|
||
|
||
The controlled manager's transport interface contains exactly:
|
||
|
||
```text
|
||
connect_loopback
|
||
send_all
|
||
close_socket
|
||
```
|
||
|
||
Source: controlled manager `include/verified_launcher.h:38-42` and
|
||
`src/ps5_launcher.c:18-69`. `verified_launcher.c:153-183` sends the header and
|
||
artifact bytes, then closes the socket. There is no receive callback, response
|
||
buffer, length framing, result deadline, or result validation.
|
||
|
||
The elfldr side can write a fixed text line after the child is reaped, but the
|
||
controlled manager has already closed and never reads it. The text carries no
|
||
nonce, request ID, process status, capability data, checksum, truncation flag,
|
||
or cleanup status. It also cannot distinguish a normal exit from a reaped
|
||
crash because the waiter discards status.
|
||
|
||
Classification: `REJECTED_SEND_ONLY_NO_RESULT_RECEIVE`.
|
||
|
||
## D3 — loader-owned fixed status record
|
||
|
||
Hardened elfldr has transient local variables for child PID, watchdog return,
|
||
and `waitpid` status, but no persistent or returned per-request status record.
|
||
The current `wait_reaped` discards the status and the connection response is
|
||
fixed.
|
||
|
||
A small loader-owned record would still require:
|
||
|
||
- per-request storage and nonce binding;
|
||
- preservation of actual wait status;
|
||
- an exact normal/error/crash mapping;
|
||
- a proven way for the observer to supply bounded data;
|
||
- a manager receive operation and response framing; and
|
||
- lifecycle rules preventing stale reuse or a race between completion and
|
||
cleanup.
|
||
|
||
Those are production loader/manager changes and were forbidden in this task.
|
||
Classification:
|
||
`UNPROVEN_REQUIRES_LOADER_STATE_AND_PROPAGATION_CHANGE`.
|
||
|
||
## D4 — process exit status
|
||
|
||
`waitpid` supplies an integer status to the loader, but
|
||
`ps5_controlled.c:36-59` does not retain or return it. The manager receives no
|
||
status. The same fixed text can follow a normal exit or a crash, and the
|
||
watchdog result distinguishes only its own high-level completion/timeout path.
|
||
|
||
Even if later propagated, a conventional exit status has too little space for
|
||
the required observations and needs an exact mapping for success, unsupported,
|
||
observer error, crash, signal, timeout, and cleanup failure. None exists.
|
||
|
||
Classification: `REJECTED_WAIT_STATUS_DISCARDED_AND_AMBIGUOUS`.
|
||
|
||
## Rejected output mechanisms
|
||
|
||
The following remain inadmissible:
|
||
|
||
- target filesystem or target logs;
|
||
- a new socket, listener, server, debug service, or callback;
|
||
- notification, klog, stdout, stderr, crash dump, or audit log;
|
||
- kernel buffer or persistent shared memory;
|
||
- autoload or service state;
|
||
- graphics, VideoOut, SDL, GNM, or RetroArch;
|
||
- timing or another covert channel; and
|
||
- the legacy stdio route.
|
||
|
||
The controlled route passes `stdio=-1` at `socksrv.c:139-145`. Stdio
|
||
duplication occurs only for `stdio > 0` at `elfldr.c:499-520`.
|
||
|
||
## Host-only D1 protocol
|
||
|
||
The protocol model is `tests/phase09c_feasibility_model.py`. It performs no
|
||
filesystem, network, compiler, or target operation.
|
||
|
||
### Ownership and completion
|
||
|
||
The conceptual write sequence is:
|
||
|
||
```text
|
||
caller:
|
||
allocate and zero exactly 4096 bytes
|
||
bind nonce, request ID, exact artifact hash and deadline
|
||
|
||
observer:
|
||
fill fixed fields and at most 3840 body bytes
|
||
write explicit status and observed/unsupported bitmaps
|
||
write body SHA-256
|
||
return through the still-unproven safe ABI
|
||
|
||
caller after proven return and cleanup:
|
||
validate request binding and body
|
||
write cleanup status
|
||
compute final result SHA-256
|
||
write completion marker last
|
||
|
||
consumer:
|
||
validate once
|
||
reject stale, duplicate, incomplete, timed-out or inconsistent records
|
||
```
|
||
|
||
The caller-finalization step is essential: an observer cannot truthfully
|
||
attest loader cleanup that occurs only after it returns. The current loader has
|
||
no such step.
|
||
|
||
### Fixed framing
|
||
|
||
All integers are unsigned big-endian. There are no pointers, offsets supplied
|
||
by the observer, variable headers, dynamic growth, or nested lengths.
|
||
|
||
| Offset | Size | Field |
|
||
|---:|---:|---|
|
||
| 0 | 8 | magic `CHG09C01` |
|
||
| 8 | 2 | protocol version `1` |
|
||
| 10 | 2 | header size `256` |
|
||
| 12 | 4 | maximum output size `4096` |
|
||
| 16 | 4 | actual output size, inclusive of header |
|
||
| 20 | 4 | observer version |
|
||
| 24 | 4 | status |
|
||
| 28 | 4 | flags; bit 0 means truncation |
|
||
| 32 | 4 | caller-recorded cleanup status |
|
||
| 36 | 4 | reserved zero |
|
||
| 40 | 8 | requested capability bitmap |
|
||
| 48 | 8 | observed capability bitmap |
|
||
| 56 | 8 | unsupported capability bitmap |
|
||
| 64 | 8 | monotonic deadline in nanoseconds |
|
||
| 72 | 16 | execution nonce |
|
||
| 88 | 16 | request ID |
|
||
| 104 | 8 | firmware source 1, canonical NUL-padded ASCII |
|
||
| 112 | 8 | firmware source 2, canonical NUL-padded ASCII |
|
||
| 120 | 32 | exact future observer artifact SHA-256 |
|
||
| 152 | 32 | SHA-256 of body bytes |
|
||
| 184 | 32 | SHA-256 of header and body with this field and completion zero |
|
||
| 216 | 8 | completion marker `COMPLETE`, written last |
|
||
| 224 | 32 | reserved zero |
|
||
| 256 | 0–3840 | bounded body |
|
||
| actual–4096 | remainder | required zero |
|
||
|
||
`actual_output_size` must be in `[256, 4096]`; subtraction and addition are
|
||
checked before any slice is accepted. The body size is exactly
|
||
`actual_output_size - 256`. A body larger than 3840 bytes is rejected unless
|
||
the model deliberately produces a truncated record, and every truncated
|
||
record is blocked.
|
||
|
||
### Status and bitmap rules
|
||
|
||
- only explicit `SUCCESS` can yield a valid completed record;
|
||
- observer error, timeout, firmware conflict, or any unknown status is
|
||
blocked;
|
||
- `observed & unsupported` must be zero;
|
||
- `observed | unsupported` must exactly equal the requested bitmap;
|
||
- unrequested bits are forbidden;
|
||
- explicit unsupported bits preserve evidence but do not prove the
|
||
corresponding capability;
|
||
- an empty successful body is valid only when the requested bitmap is also
|
||
fully accounted for;
|
||
- an empty observer-error body is still failure, never empty success.
|
||
|
||
### Fail-closed validation
|
||
|
||
The consumer rejects:
|
||
|
||
- wrong magic, unknown protocol version, header size, or maximum;
|
||
- invalid or overflowed actual size;
|
||
- nonzero reserved or unused bytes;
|
||
- missing completion marker;
|
||
- stale nonce or request ID;
|
||
- duplicate consumption;
|
||
- observer version, deadline, artifact hash, or firmware binding mismatch;
|
||
- absent second firmware source or conflicting firmware sources;
|
||
- expired deadline;
|
||
- body or final checksum failure;
|
||
- unknown flags or truncation;
|
||
- incomplete or failed cleanup;
|
||
- observer failure;
|
||
- incomplete, overlapping, or unrequested capability bitmaps.
|
||
|
||
The completion marker is not itself an integrity proof. It is accepted only
|
||
after both checksums and every binding validate.
|
||
|
||
## Firmware binding
|
||
|
||
Source 1 is SDK `kernel_get_fw_version()` at `crt/kernel.c:148-170`. It reads
|
||
the `sdk_ps5_ver` field from the `libSceLibcInternal` process parameter and is
|
||
not independent device evidence. The SDK export stubs contain the symbol name
|
||
`sceKernelGetProsperoSystemSwVersion` at
|
||
`sce_stubs/libkernel_web.c:2358-2360`, but no reviewed public header,
|
||
signature, layout, semantics, side-effect contract, or firmware-9.60 runtime
|
||
evidence accompanies that name. A symbol name is not an ABI and is not source
|
||
2.
|
||
|
||
A future source 2 must be independently sourced, publicly reviewable or
|
||
locally cryptographically bound, current for the same execution, exact rather
|
||
than normalized from an ambiguous value, and bound into the same nonce and
|
||
request record. Absence yields `BLOCKED_FIRMWARE_SOURCE_2_ABSENT`; disagreement
|
||
yields `BLOCKED_FIRMWARE_CONFLICT`. Source 1 is never silently preferred.
|
||
|
||
## Feasibility decision
|
||
|
||
The 4096-byte D1 framing is internally closed and host-testable. That proves
|
||
only the data contract. It does not prove:
|
||
|
||
- a caller-owned target buffer;
|
||
- a shared mapping or copy-out ABI;
|
||
- an observer write boundary;
|
||
- a safe return or process exit;
|
||
- caller finalization after real cleanup;
|
||
- manager delivery; or
|
||
- firmware 9.60 runtime behavior.
|
||
|
||
No current bounded non-persistent output channel exists. The output result is
|
||
therefore `BLOCKED_NO_BOUNDED_OUTPUT_CHANNEL` despite the passing host model.
|