|
| 1 | +// SPDX-License-Identifier: MIT |
| 2 | +// |
| 3 | +// Pluggable storage backend for the sealed vTPM blob. |
| 4 | +// |
| 5 | +// The vTPM persistence cycle (`Provision` → seal → store, store → load → |
| 6 | +// `Recover`) is intentionally decoupled from the underlying durable medium |
| 7 | +// so that the same crypto path can be paired with different host-side |
| 8 | +// storage strategies. Three backends are bundled: |
| 9 | +// |
| 10 | +// * `StaticBufStore` — in-CVM static buffer, survives warm reboots |
| 11 | +// within the lifetime of the CVM (test / dev). |
| 12 | +// * `IgvmVarStore` — IGVM variable interface (stubbed, TODO). |
| 13 | +// * `VsockHostStore` — AF_VSOCK to a host-side helper that persists |
| 14 | +// the blob on the host filesystem |
| 15 | +// (feature-gated `vsock`). |
| 16 | +// |
| 17 | +// The trait is intentionally backend-agnostic so that it can be paired |
| 18 | +// with the upcoming SVSM block-layer abstraction or with a KBS-backed |
| 19 | +// stateful vTPM service without changing the persistence logic itself. |
| 20 | + |
| 21 | +extern crate alloc; |
| 22 | + |
| 23 | +use alloc::vec::Vec; |
| 24 | + |
| 25 | +use crate::locking::SpinLock; |
| 26 | +use crate::protocols::errors::SvsmReqError; |
| 27 | + |
| 28 | +/// Persistent storage backend for the sealed vTPM blob. |
| 29 | +/// |
| 30 | +/// Implementations must be safe to share across CPUs (`Send + Sync`); |
| 31 | +/// concurrency control is the implementation's responsibility. |
| 32 | +pub trait SealedBlobStore: Send + Sync { |
| 33 | + /// Return the previously saved blob, if any. |
| 34 | + /// |
| 35 | + /// `Ok(None)` indicates a cold boot or an empty backend; the caller |
| 36 | + /// should fall back to `VtpmBootMode::Provision`. A backend error is |
| 37 | + /// distinct from an empty backend and is reported via `Err`. |
| 38 | + fn load(&self) -> Result<Option<Vec<u8>>, SvsmReqError>; |
| 39 | + |
| 40 | + /// Persist the given blob. |
| 41 | + /// |
| 42 | + /// Implementations should overwrite any prior content atomically when |
| 43 | + /// the backend supports it. |
| 44 | + fn save(&self, blob: &[u8]) -> Result<(), SvsmReqError>; |
| 45 | +} |
| 46 | + |
| 47 | +// ============================================================ |
| 48 | +// StaticBufStore — in-CVM static buffer |
| 49 | +// ============================================================ |
| 50 | + |
| 51 | +const STATIC_BUF_CAPACITY: usize = 4096; |
| 52 | + |
| 53 | +#[derive(Debug)] |
| 54 | +struct StaticBuf { |
| 55 | + buf: [u8; STATIC_BUF_CAPACITY], |
| 56 | + len: usize, |
| 57 | + valid: bool, |
| 58 | +} |
| 59 | + |
| 60 | +/// In-CVM static buffer. Survives warm reboots within a single CVM |
| 61 | +/// lifetime; zeroed on cold boot. Intended for development, testing, |
| 62 | +/// and bring-up scenarios where no durable backing store is available. |
| 63 | +#[derive(Debug)] |
| 64 | +pub struct StaticBufStore { |
| 65 | + inner: SpinLock<StaticBuf>, |
| 66 | +} |
| 67 | + |
| 68 | +impl StaticBufStore { |
| 69 | + /// Create an empty store. Suitable for declaring as a `static`. |
| 70 | + pub const fn new() -> Self { |
| 71 | + Self { |
| 72 | + inner: SpinLock::new(StaticBuf { |
| 73 | + buf: [0u8; STATIC_BUF_CAPACITY], |
| 74 | + len: 0, |
| 75 | + valid: false, |
| 76 | + }), |
| 77 | + } |
| 78 | + } |
| 79 | + |
| 80 | + /// Storage capacity in bytes. |
| 81 | + pub const fn capacity(&self) -> usize { |
| 82 | + STATIC_BUF_CAPACITY |
| 83 | + } |
| 84 | +} |
| 85 | + |
| 86 | +impl Default for StaticBufStore { |
| 87 | + fn default() -> Self { |
| 88 | + Self::new() |
| 89 | + } |
| 90 | +} |
| 91 | + |
| 92 | +impl SealedBlobStore for StaticBufStore { |
| 93 | + fn load(&self) -> Result<Option<Vec<u8>>, SvsmReqError> { |
| 94 | + let g = self.inner.lock(); |
| 95 | + if g.valid && g.len > 0 { |
| 96 | + Ok(Some(g.buf[..g.len].to_vec())) |
| 97 | + } else { |
| 98 | + Ok(None) |
| 99 | + } |
| 100 | + } |
| 101 | + |
| 102 | + fn save(&self, blob: &[u8]) -> Result<(), SvsmReqError> { |
| 103 | + if blob.len() > STATIC_BUF_CAPACITY { |
| 104 | + log::error!( |
| 105 | + "StaticBufStore::save — blob {} exceeds capacity {}", |
| 106 | + blob.len(), |
| 107 | + STATIC_BUF_CAPACITY |
| 108 | + ); |
| 109 | + return Err(SvsmReqError::invalid_request()); |
| 110 | + } |
| 111 | + let mut g = self.inner.lock(); |
| 112 | + g.buf[..blob.len()].copy_from_slice(blob); |
| 113 | + g.len = blob.len(); |
| 114 | + g.valid = true; |
| 115 | + Ok(()) |
| 116 | + } |
| 117 | +} |
| 118 | + |
| 119 | +// ============================================================ |
| 120 | +// IgvmVarStore — stub for future IGVM variable backend |
| 121 | +// ============================================================ |
| 122 | + |
| 123 | +/// IGVM variable-backed store. Reserved for the production path; the |
| 124 | +/// load/save plumbing through the IGVM variable interface is not yet |
| 125 | +/// implemented in this tree. |
| 126 | +#[derive(Debug)] |
| 127 | +pub struct IgvmVarStore; |
| 128 | + |
| 129 | +impl IgvmVarStore { |
| 130 | + pub const fn new() -> Self { |
| 131 | + Self |
| 132 | + } |
| 133 | +} |
| 134 | + |
| 135 | +impl Default for IgvmVarStore { |
| 136 | + fn default() -> Self { |
| 137 | + Self::new() |
| 138 | + } |
| 139 | +} |
| 140 | + |
| 141 | +impl SealedBlobStore for IgvmVarStore { |
| 142 | + fn load(&self) -> Result<Option<Vec<u8>>, SvsmReqError> { |
| 143 | + log::debug!("IgvmVarStore::load — not yet implemented, returning empty"); |
| 144 | + Ok(None) |
| 145 | + } |
| 146 | + |
| 147 | + fn save(&self, _blob: &[u8]) -> Result<(), SvsmReqError> { |
| 148 | + log::warn!("IgvmVarStore::save — not yet implemented, silently dropped"); |
| 149 | + Ok(()) |
| 150 | + } |
| 151 | +} |
| 152 | + |
| 153 | +// ============================================================ |
| 154 | +// VsockHostStore — AF_VSOCK to a host-side persistence helper |
| 155 | +// ============================================================ |
| 156 | + |
| 157 | +/// VSOCK-backed store. Persists the sealed blob via a host-side helper |
| 158 | +/// reachable over AF_VSOCK. The two ports separate the load and save |
| 159 | +/// directions so that the helper can implement them as independent |
| 160 | +/// services if desired. |
| 161 | +#[cfg(feature = "vsock")] |
| 162 | +#[derive(Debug)] |
| 163 | +pub struct VsockHostStore { |
| 164 | + pub cid: u32, |
| 165 | + pub load_port: u32, |
| 166 | + pub save_port: u32, |
| 167 | +} |
| 168 | + |
| 169 | +#[cfg(feature = "vsock")] |
| 170 | +impl VsockHostStore { |
| 171 | + /// Default VSOCK ports: 9997 for load, 9998 for save. |
| 172 | + pub const DEFAULT_LOAD_PORT: u32 = 9997; |
| 173 | + pub const DEFAULT_SAVE_PORT: u32 = 9998; |
| 174 | + |
| 175 | + pub const fn new(cid: u32, load_port: u32, save_port: u32) -> Self { |
| 176 | + Self { |
| 177 | + cid, |
| 178 | + load_port, |
| 179 | + save_port, |
| 180 | + } |
| 181 | + } |
| 182 | +} |
| 183 | + |
| 184 | +#[cfg(feature = "vsock")] |
| 185 | +impl SealedBlobStore for VsockHostStore { |
| 186 | + fn load(&self) -> Result<Option<Vec<u8>>, SvsmReqError> { |
| 187 | + // TODO: open AF_VSOCK(cid, load_port), read framed blob. |
| 188 | + log::debug!( |
| 189 | + "VsockHostStore::load(cid={}, port={}) — not yet wired", |
| 190 | + self.cid, |
| 191 | + self.load_port |
| 192 | + ); |
| 193 | + Ok(None) |
| 194 | + } |
| 195 | + |
| 196 | + fn save(&self, blob: &[u8]) -> Result<(), SvsmReqError> { |
| 197 | + // TODO: open AF_VSOCK(cid, save_port), write framed blob. |
| 198 | + log::debug!( |
| 199 | + "VsockHostStore::save(cid={}, port={}, {} bytes) — not yet wired", |
| 200 | + self.cid, |
| 201 | + self.save_port, |
| 202 | + blob.len() |
| 203 | + ); |
| 204 | + Ok(()) |
| 205 | + } |
| 206 | +} |
0 commit comments