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