Conversation
daf28d3 to
bcdec55
Compare
e66be4b to
3843315
Compare
| activate Orch | ||
| Orch->>FD: channel_transact: QueryStatus | ||
| FD-->>Orch: Status::ActivationPending | ||
| Note right of Orch: bump SVN (irreversible),<br/>close SMC write filter |
There was a problem hiding this comment.
Iirc, the SVN bumping should not happen automatically but only via a manual invocation, once the firmware is considered proven in use? @FerralCoder
4242d48 to
f1b64a2
Compare
f1b64a2 to
493bd7d
Compare
|
|
||
| Design decisions: | ||
|
|
||
| - ServiceCall is the universal IPC primitive. Every cross-process request |
There was a problem hiding this comment.
Worth naming which pw_kernel syscalls start()/signal()/try_recv() map to — e.g. channel_async_transact + channel_async_transact_complete, with the initiator handle added to the caller's WaitGroup for signal().
One implication worth calling out here:
channel_async_transact is unsafe specifically because its send/recv buffers must stay "pinned" until the transaction completes, so ServiceCall<Req, Resp> can't hand start() transient stack buffers — it needs to own Req/Resp for the call's lifetime.
struct IpcPlatform {
// Pinned until try_complete() -- can't share one scratch buffer across
// in-flight channels; each gets its own fixed-size buffer, held here.
gpio_buf: (SendBuf, RecvBuf),
wdt_buf: (SendBuf, RecvBuf),
mailbox_buf: (SendBuf, RecvBuf),
power_buf: (SendBuf, RecvBuf),
}
Alternative to OpenPRoT#458. The FD owns the dispatch loop and the PLDM state machine; the orchestrator is its client and grants or denies each phase (AcceptOffer, GrantVerify, GrantApply, GrantActivate, GrantSvnCommit). Firmware bytes never pass through the orchestrator: FdOps::download_fw_data writes via the device server and FdOps::verify delegates to the crypto service, which reads the staged image from the device server itself. Every cross-process request is a ServiceCall whose completion signal sits in the caller's WaitGroup, so no process blocks on another and the orchestrator stays reactive to CompromiseDetected and its boot watchdogs. The grant gates add no PLDM states: pldm-lib polls verify and apply through fd_progress and our implementation reports 0% until the grant arrives, which keeps the MCTP responder live during the wait. The security revision is not bumped at activation. Bumping there would put the superseded image below the floor and make a trial-boot revert impossible, so the UA sets Security Revision Number Delayed Update and sends UpdateSecurityRevision (0x22) once it is satisfied with the running image. Five crates, only the server-runtime and the client-ipc kernel-tagged, so the wire and dispatch run host-side against a loopback. Write containment has two layers, a device-server staging window and the SMC write filter. Both close before GrantVerify, because a verdict over flash that can still be written does not match the image that apply commits. Both cover staging only; apply and activate stay reachable by a compromised FD and that gap is stated, not closed. Open questions are listed at the end of the doc, FD death answered with restart-and-nudge. Assisted-by: Claude
0840dbc to
583182b
Compare
|
@chrysh, I believe this is a more suitable design than the original proposal. If we are in agreement, given you stated that we should agree as a group to move on from that proposal, we can declare the orchestrator as a channel handler defunct and converge on this. Let's present this to the pigweed team so they can give us advice. |
@rusty1968 sounds good. We also had a look together with @leongross at osfc2026. How do we present it to the pigweed people? Can you do that? And how can I help? |
Human TL;DR: The design is based on those requirements: PLDM-FD and Orchestrator shall not block. PLDM is IPC-Server. --> nudge from PLDM-FD, orchestrator queries what nudge was about. Other services need to use signals to inform orchestrator async about work being finished (FW written to flash, verification done, etc)
Orchestratrator decides whether FW-Update steps are continued or vetoed. --> Therefore the Orchestrator gives out grants.
PLDM-FD automatically goes through the steps fw_data_download, verify, apply. So the functions FdOps::fw_data_download, FdOps::verify, FdOps::apply are automatically called by PLDM-FD. FdOps::* calls those functions in a loop chunkwise until the callee fills the u8-progress-value with "100" (signifying 100%).
Crypto service can read firmware data directly from Flash service without going through orchestrator.
AI-Summary
Alternative to #458: PLDM-FD is the IPC server, orchestrator is its client.
opdecoded throughTryFrom<u8>so an unknown value is a decode error; each side treats the other as untrusted and neither dispatch panicsFull design doc:
docs/src/design/orchestrator/pldm-server-ipc.mdIn-transport sequence
sequenceDiagram participant UA as UA (BMC)<br/>remote, over MCTP participant FD as PLDM-FD (server)<br/>dispatch loop + run_terminus participant Orch as Orchestrator (client)<br/>ServiceCall participant DevSrv as Device Server<br/>manages the SPI flash participant Crypto as Crypto Service<br/>hash + signature verification Note over UA, Crypto: Blue background: orchestrator IPC. Green background: FdOps service IPC. Note over UA, Orch: NEGOTIATION (PLDM protocol, MCTP only) UA->>FD: RequestUpdate (MCTP) FD-->>UA: RequestUpdate response (accepted) UA->>FD: PassComponentTable (MCTP) FD-->>UA: PassComponentTable response UA->>FD: UpdateComponent (MCTP) FD-->>UA: UpdateComponent response Note over FD, Orch: FD has an offer, nudge the orchestrator FD->>Orch: USER signal (nudge: offer ready) rect rgb(230, 240, 255) activate Orch Orch->>FD: ServiceCall: QueryStatus FD-->>Orch: Status::OfferPending { target, total, mode: InTransport } Note right of Orch: validate target + total,<br/>platform driver picks<br/>staging address,<br/>reserve staging,<br/>open SMC write filter Orch->>FD: ServiceCall: AcceptOffer { base: FlashAddress } FD-->>Orch: Ok deactivate Orch end Note over UA, DevSrv: TRANSFER (FD pulls from UA, FdOps writes to flash) rect rgb(230, 255, 230) loop FdOps::fw_data_download per chunk FD->>UA: RequestFirmwareData (MCTP) UA-->>FD: firmware chunk FD->>DevSrv: ServiceCall: write chunk DevSrv-->>FD: signal: Ok end end Note over FD, Orch: transfer done, ask orchestrator to grant verify FD->>UA: TransferComplete (MCTP) FD->>Orch: USER signal (nudge: verify pending) rect rgb(230, 240, 255) activate Orch Orch->>FD: ServiceCall: QueryStatus FD-->>Orch: Status::VerifyPending Note right of Orch: check isolation, update policy Orch->>FD: ServiceCall: GrantVerify FD-->>Orch: Ok deactivate Orch end Note over FD, Crypto: FdOps::verify rect rgb(230, 255, 230) FD->>Crypto: ServiceCall::start(VerifyRequest { addr, size }) Crypto->>DevSrv: read staged image DevSrv-->>Crypto: image data loop fd_progress poll Note over FD: verify() returns 0%, no signal yet end Crypto-->>FD: signal: Verdict Note over FD: verify() poll: try_recv -> 100% + verdict end FD->>UA: VerifyComplete (MCTP) Note over FD, Orch: verify done, ask orchestrator to grant apply FD->>Orch: USER signal (nudge: apply pending) rect rgb(230, 240, 255) activate Orch Orch->>FD: ServiceCall: QueryStatus FD-->>Orch: Status::ApplyPending { verify_ok: true } Orch->>FD: ServiceCall: GrantApply FD-->>Orch: Ok deactivate Orch end Note over FD, DevSrv: FdOps::apply rect rgb(230, 255, 230) FD->>DevSrv: ServiceCall::start(apply: commit staged image) loop fd_progress poll Note over FD: apply() returns 0%, no signal yet end DevSrv-->>FD: signal: Ok Note over FD: apply() poll: try_recv -> 100% end FD->>UA: ApplyComplete (MCTP) Note over FD, Orch: ACTIVATION (FdOps::activate) UA->>FD: ActivateFirmware (MCTP) FD->>Orch: USER signal (nudge: activation requested) rect rgb(230, 240, 255) activate Orch Orch->>FD: ServiceCall: QueryStatus FD-->>Orch: Status::ActivationPending Note right of Orch: close SMC write filter Orch->>FD: ServiceCall: Activate FD-->>Orch: Ok deactivate Orch end rect rgb(230, 255, 230) FD->>DevSrv: ServiceCall: FdOps::activate: set boot preference DevSrv-->>FD: signal: Ok end FD-->>UA: ActivateFirmware response (accepted) Note over UA, DevSrv: SVN COMMIT (later, FD back in IDLE, new image running) Note right of Orch: judge the boot, then<br/>TrialBoot::confirm or revert UA->>FD: UpdateSecurityRevision (MCTP, 0x22) FD->>Orch: USER signal (nudge: SVN commit requested) rect rgb(230, 240, 255) activate Orch Orch->>FD: ServiceCall: QueryStatus FD-->>Orch: Status::SvnCommitPending { component } Note right of Orch: a confirmed trial only,<br/>else DenySvnCommit Orch->>DevSrv: ServiceCall: SvnFloor::advance DevSrv-->>Orch: signal: Ok Orch->>FD: ServiceCall: GrantSvnCommit FD-->>Orch: Ok deactivate Orch end FD-->>UA: UpdateSecurityRevision response (success) Note over UA, Orch: CANCEL (between AcceptOffer and Activate) UA->>FD: CancelUpdate (MCTP) FD->>Orch: USER signal (nudge: cancelled) rect rgb(230, 240, 255) activate Orch Orch->>FD: ServiceCall: QueryStatus FD-->>Orch: Status::Cancelled Note right of Orch: release staging,<br/>close SMC write filter Orch->>FD: ServiceCall: AckCancel FD-->>Orch: Ok deactivate Orch end FD-->>UA: CancelUpdate responseOut-of-transport sequence
sequenceDiagram participant UA as UA (BMC)<br/>remote, over MCTP participant FD as PLDM-FD (server)<br/>dispatch loop + run_terminus participant Orch as Orchestrator (client)<br/>ServiceCall participant DevSrv as Device Server<br/>manages the SPI flash participant Crypto as Crypto Service<br/>hash + signature verification Note over UA, Crypto: Blue background: orchestrator IPC. Green background: FdOps service IPC. Note over UA, Orch: NEGOTIATION (same as in-transport) UA->>FD: RequestUpdate (MCTP) FD-->>UA: RequestUpdate response (accepted) UA->>FD: PassComponentTable (MCTP) FD-->>UA: PassComponentTable response UA->>FD: UpdateComponent (MCTP, out-of-transport) FD-->>UA: UpdateComponent response Note over FD, Orch: FD has an offer, nudge the orchestrator FD->>Orch: USER signal (nudge: offer ready) rect rgb(230, 240, 255) activate Orch Orch->>FD: ServiceCall: QueryStatus FD-->>Orch: Status::OfferPending { target, total, mode: OutOfTransport } Note right of Orch: validate target + total,<br/>platform driver picks<br/>staging address Orch->>FD: ServiceCall: AcceptOffer { base: FlashAddress } Note left of FD: FD does not write in<br/>out-of-transport, but the<br/>orchestrator communicates the<br/>base address to the third party<br/>that pre-stages the image FD-->>Orch: Ok deactivate Orch end Note over FD, Orch: no transfer phase, image already staged FD->>UA: TransferComplete (MCTP) Note over FD, Orch: ask orchestrator to grant verify FD->>Orch: USER signal (nudge: verify pending) rect rgb(230, 240, 255) activate Orch Orch->>FD: ServiceCall: QueryStatus FD-->>Orch: Status::VerifyPending Note right of Orch: check isolation, update policy Orch->>FD: ServiceCall: GrantVerify FD-->>Orch: Ok deactivate Orch end Note over FD, Crypto: FdOps::verify rect rgb(230, 255, 230) FD->>Crypto: ServiceCall::start(VerifyRequest { addr, size }) Crypto->>DevSrv: read staged image DevSrv-->>Crypto: image data loop fd_progress poll Note over FD: verify() returns 0%, no signal yet end Crypto-->>FD: signal: Verdict Note over FD: verify() poll: try_recv -> 100% + verdict end FD->>UA: VerifyComplete (MCTP) Note over FD, Orch: verify done, ask orchestrator to grant apply FD->>Orch: USER signal (nudge: apply pending) rect rgb(230, 240, 255) activate Orch Orch->>FD: ServiceCall: QueryStatus FD-->>Orch: Status::ApplyPending { verify_ok: true } Orch->>FD: ServiceCall: GrantApply FD-->>Orch: Ok deactivate Orch end Note over FD, DevSrv: FdOps::apply rect rgb(230, 255, 230) FD->>DevSrv: ServiceCall::start(apply: commit staged image) loop fd_progress poll Note over FD: apply() returns 0%, no signal yet end DevSrv-->>FD: signal: Ok Note over FD: apply() poll: try_recv -> 100% end FD->>UA: ApplyComplete (MCTP) Note over FD, Orch: ACTIVATION (FdOps::activate, same as in-transport) UA->>FD: ActivateFirmware (MCTP) FD->>Orch: USER signal (nudge: activation requested) rect rgb(230, 240, 255) activate Orch Orch->>FD: ServiceCall: QueryStatus FD-->>Orch: Status::ActivationPending Orch->>FD: ServiceCall: Activate FD-->>Orch: Ok deactivate Orch end rect rgb(230, 255, 230) FD->>DevSrv: ServiceCall: FdOps::activate: set boot preference DevSrv-->>FD: signal: Ok end FD-->>UA: ActivateFirmware response (accepted) Note over UA, DevSrv: SVN COMMIT (later, FD back in IDLE, new image running) Note right of Orch: judge the boot, then<br/>TrialBoot::confirm or revert UA->>FD: UpdateSecurityRevision (MCTP, 0x22) FD->>Orch: USER signal (nudge: SVN commit requested) rect rgb(230, 240, 255) activate Orch Orch->>FD: ServiceCall: QueryStatus FD-->>Orch: Status::SvnCommitPending { component } Note right of Orch: a confirmed trial only,<br/>else DenySvnCommit Orch->>DevSrv: ServiceCall: SvnFloor::advance DevSrv-->>Orch: signal: Ok Orch->>FD: ServiceCall: GrantSvnCommit FD-->>Orch: Ok deactivate Orch end FD-->>UA: UpdateSecurityRevision response (success) Note over UA, Orch: CANCEL (between AcceptOffer and Activate) UA->>FD: CancelUpdate (MCTP) FD->>Orch: USER signal (nudge: cancelled) rect rgb(230, 240, 255) activate Orch Orch->>FD: ServiceCall: QueryStatus FD-->>Orch: Status::Cancelled Note right of Orch: discard the accepted offer Orch->>FD: ServiceCall: AckCancel FD-->>Orch: Ok deactivate Orch end FD-->>UA: CancelUpdate responseSecurity revision commit
Activation does not raise the anti-rollback floor. If it did, a trial boot
could never be reverted: the superseded image would sit below the new floor
and refuse to run. DSP0267 1.3.0 keeps the two apart. The UA sets the
Security Revision Number Delayed Update option (
UpdateOptionFlagsbit 2) onUpdateComponent, the FD applies and activates without touching the revision,
and the UA sends UpdateSecurityRevision (command 0x22, section 12.19) once it
is satisfied with the image. Until that command arrives a downgrade is still
allowed, which is the window the trial boot lives in.
The FD accepts 0x22 only in the IDLE state, so it arrives outside update mode,
minutes or days after activation. It acts on the active running image, not a
pending one, which is what makes it safe to gate on the boot verdict.
The orchestrator owns the write. On a confirmed trial it advances
SvnFloorfor that component, then sends GrantSvnCommit so the FD can answer the UA. The
FD relays the request and the answer and never touches the floor, the same
split the rest of this design uses for anything irreversible. On an
unconfirmed or absent trial the orchestrator denies with PolicyViolation and
the FD returns UPDATE_SECURITY_REVISION_NOT_PERMITTED. DSP0267 has no code for
a policy refusal, so that capability code is the nearest fit.
IPC operations
FdOps and IPC services
Test plan
Assisted-by: Claude