Skip to content

Commit 6f5db7b

Browse files
committed
vtpm/svsm: Wire TPM-rooted persistence boot mode
Wires the seal/unseal lifecycle into kernel init using the previously added components: * Adds VtpmBootMode (Provision/Recover) and vtpm_init_sealed<T> in vtpm/mod.rs. Provision manufactures the vTPM, extracts the serialized internal state, AES-256-GCMs it, TPM2_Seals the 60-byte key bundle and returns a SealedBlob ready for storage. Recover reverses the operation: unpack -> TPM2_Unseal -> AES decrypt -> inject_serialized_state -> light power-on. * Replaces the previous `static mut SEALED_BLOB_BUF: [u8; 4096]` scaffolding in svsm.rs with a `&dyn SealedBlobStore` wired through StaticBufStore. Whether the blob comes from a static buffer (test), an IGVM variable (production), or a vsock-backed host helper is now a backend selection rather than a code change. Entropy comes from RDSEED inside the CVM; key material is zeroized after the sealing step via volatile writes. The boot integration is intentionally kept narrow so that a future user-mode vTPM placement (per the upstream "Move vTPM to user-mode" plan) can reuse the same persistence trait by selecting a different SealedBlobStore impl. Signed-off-by: Goodleon-Y <goodleon3@126.com>
1 parent 143ab03 commit 6f5db7b

2 files changed

Lines changed: 246 additions & 2 deletions

File tree

kernel/src/svsm.rs

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,14 @@ use svsm::utils::ScopedMut;
7272
use svsm::utils::round_to_pages;
7373
#[cfg(all(feature = "virtio-drivers", any(feature = "block", feature = "vsock")))]
7474
use svsm::virtio::probe_mmio_slots;
75+
#[cfg(all(feature = "vtpm", not(feature = "vsock"), not(test)))]
76+
use svsm::vtpm::MockTransport;
77+
#[cfg(all(feature = "vtpm", feature = "vsock", not(test)))]
78+
use svsm::vtpm::VsockTransport;
7579
#[cfg(all(feature = "vtpm", not(test)))]
76-
use svsm::vtpm::vtpm_init;
80+
use svsm::vtpm::{SealedBlobStore, StaticBufStore, VtpmBootMode, vtpm_init_sealed};
81+
#[cfg(all(feature = "vtpm", feature = "vsock", not(test)))]
82+
use svsm::vtpm::{VSOCK_HOST_CID, VSOCK_TPM_PORT};
7783

7884
use alloc::string::String;
7985
use release::COCONUT_VERSION;
@@ -553,7 +559,47 @@ fn svsm_init(launch_info: &KernelLaunchInfo) {
553559
}
554560

555561
#[cfg(all(feature = "vtpm", not(test)))]
556-
vtpm_init().expect("vTPM failed to initialize");
562+
{
563+
// SealedBlob persistence is driven through the `SealedBlobStore`
564+
// trait so that the same code path can target any backend
565+
// (warm-reboot static buffer for dev/test, IGVM variable, the
566+
// upcoming SVSM block layer, KBS-stateful vTPM service, ...).
567+
// The default in this tree is the static buffer backend, which
568+
// survives warm reboots within a single CVM lifetime.
569+
static SEALED_BLOB_STORE: StaticBufStore = StaticBufStore::new();
570+
let store: &dyn SealedBlobStore = &SEALED_BLOB_STORE;
571+
572+
let blob_opt = store.load().expect("SealedBlobStore::load failed");
573+
let (boot_mode, blob_slice) = match blob_opt.as_deref() {
574+
Some(b) => (VtpmBootMode::Recover, Some(b)),
575+
None => (VtpmBootMode::Provision, None),
576+
};
577+
578+
let vm_id: [u8; 16] = [0u8; 16]; // TODO: derive from IGVM guest_context
579+
580+
#[cfg(feature = "vsock")]
581+
let transport = VsockTransport::new(VSOCK_HOST_CID, VSOCK_TPM_PORT);
582+
#[cfg(not(feature = "vsock"))]
583+
let transport = {
584+
log::warn!("VTPM: no VSOCK, using MockTransport (debug only)");
585+
MockTransport::new()
586+
};
587+
588+
let maybe_blob = vtpm_init_sealed(transport, boot_mode, vm_id, blob_slice)
589+
.expect("vTPM sealed init failed");
590+
591+
if let Some(blob_bytes) = maybe_blob {
592+
store
593+
.save(&blob_bytes)
594+
.expect("SealedBlobStore::save failed");
595+
log::info!(
596+
"VTPM: SealedBlob ({} bytes) stored for next boot",
597+
blob_bytes.len()
598+
);
599+
}
600+
601+
log::info!("VTPM: sealed init complete (mode={boot_mode:?})");
602+
}
557603

558604
virt_log_usage();
559605

kernel/src/vtpm/mod.rs

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,20 @@ pub trait VtpmInterface: TcgTpmSimulatorInterface {
105105
fn get_ekpub(&mut self) -> Result<Vec<u8>, SvsmReqError>;
106106
}
107107

108+
/// vTPM boot mode driving the persistence cycle.
109+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110+
pub enum VtpmBootMode {
111+
/// First boot: manufacture vTPM, extract internal state, seal to the
112+
/// external TPM, return the sealed blob for host-side storage.
113+
Provision,
114+
/// Subsequent boot: unseal the sealed blob, inject state, light power-on.
115+
Recover,
116+
}
117+
118+
/// VSOCK addressing for the host-side TPM endpoint.
119+
pub const VSOCK_HOST_CID: u32 = 2;
120+
pub const VSOCK_TPM_PORT: u32 = 9999;
121+
108122
static VTPM: SpinLock<Vtpm> = SpinLock::new(Vtpm::new());
109123

110124
/// Initialize the TPM by calling the init() implementation of the
@@ -118,6 +132,180 @@ pub fn vtpm_init() -> Result<(), SvsmReqError> {
118132
Ok(())
119133
}
120134

135+
/// vTPM initialization with seal/unseal integration.
136+
///
137+
/// Provision mode: manufacture vTPM, extract internal state, AES-256-GCM
138+
/// encrypt, TPM2_Seal the key bundle, return the packed SealedBlob bytes.
139+
/// Recover mode: unpack the SealedBlob, TPM2_Unseal the key bundle, AES
140+
/// decrypt, inject internal state, perform a light power-on.
141+
pub fn vtpm_init_sealed<T: TpmTransport>(
142+
transport: T,
143+
mode: VtpmBootMode,
144+
vm_id: [u8; 16],
145+
sealed_blob_data: Option<&[u8]>,
146+
) -> Result<Option<Vec<u8>>, SvsmReqError> {
147+
let mut vtpm = VTPM.lock();
148+
if vtpm.is_powered_on() {
149+
return Ok(None);
150+
}
151+
152+
let mut proxy = TpmProxy::new(transport);
153+
154+
match mode {
155+
VtpmBootMode::Provision => {
156+
vtpm.init()?;
157+
log::info!("VTPM: manufactured (Provision mode)");
158+
159+
// Bulk-serialize the TPM internal state (seeds, PCR save area,
160+
// auth values, counters) into an opaque byte buffer. This is
161+
// round-tripped via inject_serialized_state() on Recover.
162+
let serialized = state::extract_serialized_state()?;
163+
log::info!(
164+
"VTPM: internal state extracted ({} bytes)",
165+
serialized.len()
166+
);
167+
168+
let vtpm_state = VtpmState {
169+
ek_priv: Vec::new(),
170+
ek_pub: vtpm.get_ekpub()?,
171+
srk_priv: Vec::new(),
172+
srk_pub: Vec::new(),
173+
owner_auth: [0u8; 32],
174+
endorsement_auth: [0u8; 32],
175+
lockout_auth: [0u8; 32],
176+
nv_data: Vec::new(),
177+
nv_counter: 0,
178+
platform_auth: [0u8; 32],
179+
extra: serialized,
180+
};
181+
182+
let (aes_key, nonce) = platform_entropy()?;
183+
let blob = sealed::seal_state(&mut proxy, &vtpm_state, vm_id, &aes_key, &nonce)
184+
.map_err(|_| SvsmReqError::invalid_request())?;
185+
zeroize_key_material(&aes_key, &nonce);
186+
187+
log::info!(
188+
"VTPM: state sealed to TPM (counter={}, enc_size={})",
189+
blob.counter,
190+
blob.encrypted_data.len()
191+
);
192+
193+
proxy.flush_primary();
194+
Ok(Some(blob.pack()))
195+
}
196+
197+
VtpmBootMode::Recover => {
198+
let blob_data = sealed_blob_data.ok_or_else(|| {
199+
log::error!("VTPM: Recover mode requires sealed_blob_data");
200+
SvsmReqError::invalid_request()
201+
})?;
202+
203+
let blob = SealedBlob::unpack(blob_data).map_err(|_| {
204+
log::error!("VTPM: failed to unpack SealedBlob");
205+
SvsmReqError::invalid_request()
206+
})?;
207+
208+
log::info!("VTPM: SealedBlob loaded (counter={})", blob.counter);
209+
210+
let vtpm_state = sealed::unseal_state(&mut proxy, &blob).map_err(|_| {
211+
log::error!("VTPM: unseal_state failed");
212+
SvsmReqError::invalid_request()
213+
})?;
214+
215+
proxy.flush_primary();
216+
217+
// Inject the serialized internal state back into the TPM via the
218+
// C-side bulk deserializer.
219+
state::inject_serialized_state(&vtpm_state.extra).map_err(|_| {
220+
log::error!("VTPM: inject_serialized_state failed");
221+
SvsmReqError::invalid_request()
222+
})?;
223+
224+
log::info!("VTPM: internal state injected");
225+
226+
vtpm.signal_poweron(false)?;
227+
vtpm.signal_nvon()?;
228+
229+
log::info!(
230+
"VTPM: state recovered from TPM seal (counter={})",
231+
blob.counter
232+
);
233+
234+
Ok(None)
235+
}
236+
}
237+
}
238+
239+
/// Generate an AES-256 key and a GCM nonce from the platform entropy source
240+
/// (RDSEED).
241+
fn platform_entropy() -> Result<([u8; 32], [u8; 12]), SvsmReqError> {
242+
let mut key = [0u8; 32];
243+
let mut nonce = [0u8; 12];
244+
245+
// SAFETY: `_rdseed64_step` is a CPU intrinsic with no memory side-effects
246+
// beyond the pointed-to u64; pointers come from properly aligned slices.
247+
unsafe {
248+
for i in 0..4 {
249+
let mut retries = 0;
250+
loop {
251+
if core::arch::x86_64::_rdseed64_step(
252+
&mut *(key[i * 8..][..8].as_mut_ptr() as *mut u64),
253+
) == 1
254+
{
255+
break;
256+
}
257+
retries += 1;
258+
if retries > 100 {
259+
return Err(SvsmReqError::invalid_request());
260+
}
261+
core::arch::x86_64::_mm_pause();
262+
}
263+
}
264+
let mut retries = 0;
265+
loop {
266+
if core::arch::x86_64::_rdseed64_step(&mut *(nonce[..8].as_mut_ptr() as *mut u64)) == 1
267+
{
268+
break;
269+
}
270+
retries += 1;
271+
if retries > 100 {
272+
return Err(SvsmReqError::invalid_request());
273+
}
274+
core::arch::x86_64::_mm_pause();
275+
}
276+
let mut retries = 0;
277+
loop {
278+
if core::arch::x86_64::_rdseed64_step(&mut *(nonce[8..].as_mut_ptr() as *mut u64)) == 1
279+
{
280+
break;
281+
}
282+
retries += 1;
283+
if retries > 100 {
284+
return Err(SvsmReqError::invalid_request());
285+
}
286+
core::arch::x86_64::_mm_pause();
287+
}
288+
}
289+
290+
Ok((key, nonce))
291+
}
292+
293+
/// Zeroize key material in-place using volatile writes.
294+
fn zeroize_key_material(aes_key: &[u8; 32], nonce: &[u8; 12]) {
295+
// SAFETY: volatile writes into stack-owned arrays whose lifetimes are
296+
// still live; pointers are valid for `write_volatile` and within bounds.
297+
unsafe {
298+
let key_ptr = aes_key.as_ptr() as *mut u8;
299+
for i in 0..32 {
300+
key_ptr.add(i).write_volatile(0);
301+
}
302+
let nonce_ptr = nonce.as_ptr() as *mut u8;
303+
for i in 0..12 {
304+
nonce_ptr.add(i).write_volatile(0);
305+
}
306+
}
307+
}
308+
121309
pub fn vtpm_get_locked<'a>() -> LockGuard<'a, Vtpm> {
122310
VTPM.lock()
123311
}
@@ -128,3 +316,13 @@ pub fn vtpm_get_manifest() -> Result<Vec<u8>, SvsmReqError> {
128316
let mut vtpm = VTPM.lock();
129317
vtpm.get_ekpub()
130318
}
319+
320+
// Re-export sealed/proxy/state types for use by kernel init (svsm.rs)
321+
#[cfg(feature = "vsock")]
322+
pub use proxy::VsockTransport;
323+
pub use proxy::{MockTransport, TpmProxy, TpmTransport};
324+
pub use sealed::{SealedBlob, VtpmState};
325+
#[cfg(feature = "vsock")]
326+
pub use sealed_store::VsockHostStore;
327+
pub use sealed_store::{IgvmVarStore, SealedBlobStore, StaticBufStore};
328+
pub use state::VtpmInternalState;

0 commit comments

Comments
 (0)