Skip to content

Commit b1fbbd1

Browse files
committed
vtpm: Add runtime re-seal hook and NV-block persistence
Adds a runtime re-seal path that fires on a successful guest TPM2_Shutdown (cmdCode 0x145, rc=0) and the supporting Tier-2 NV-block extraction needed to round-trip the simulator's persistent state across a cold boot. * libtcgtpm: expose get_nv_blob() / apply_nv_blob() accessors so the in-process TPM Reference Implementation's 16 KiB NV image (STATE_NV_BACKUP_SIZE) can be captured at re-seal time and replayed on Recover. The bulk serialization wrapper grows a new tagged section (0x12) that carries the NV blob alongside the existing gp/gc/gr globals. * vtpm::reseal: new module hosting trigger_if_shutdown() and do_reseal(). The hook is invoked from TcgTpm::send_tpm_command *outside* the VTPM SpinLock — re-seal talks to the TPM endpoint through a separate TpmProxy<VsockTransport> and reads internal state through the libtcgtpm C FFI, so it never re-acquires VTPM. Re-seal failures are logged but never propagated to the guest; fail-closed enforcement remains on the next cold-boot unseal path (NV-counter check + AES-GCM tag verify). * vtpm::mod / svsm: wire the optional vtpm-persist feature so the TPM endpoint and host blob store can be reached over VSOCK during cold-boot Provision/Recover and runtime re-seal. The persistence path is opt-in and never silently falls back to ephemeral — an unreachable endpoint is a hard error so the security model that treats the physical TPM as a separate root of trust cannot be downgraded by a channel-only attacker. * cpu::apic / sev::snp_apic: convert the previously panicking icr_write() to a logged warning so a transient HV write error on the persistence path does not crash the boot CPU. Signed-off-by: Goodleon-Y <goodleon3@126.com>
1 parent 6f5db7b commit b1fbbd1

15 files changed

Lines changed: 1126 additions & 104 deletions

File tree

configs/qemu-target.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
},
1515
"kernel": {
1616
"svsm": {
17-
"features": "vtpm",
17+
"features": "vtpm,virtio-drivers,vsock",
1818
"binary": false
1919
},
2020
"stage2": {

kernel/Cargo.toml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,26 @@ virtio-drivers = ["dep:virtio-drivers"]
8686
block = []
8787
vsock = []
8888

89+
# Persistent vTPM via TPM-sealing of internal state to an off-CVM TPM
90+
# endpoint (research extension; mainline behaviour preserved when off).
91+
#
92+
# When OFF (default): the `vtpm` feature provides the standard ephemeral
93+
# vTPM that the mainline COCONUT-SVSM tree exposes — each cold boot
94+
# re-manufactures the simulator and produces a fresh EK/SRK/IAK.
95+
#
96+
# When ON: svsm.rs drives the Provision/Recover seal cycle through a
97+
# `VsockTransport`/`VsockHostStore` pair. The host helper (TPM endpoint
98+
# on a VSOCK port + blob store on two VSOCK ports) must be reachable;
99+
# unreachable endpoints are a fatal error, not a silent fallback,
100+
# because silent fallback would weaken the security model that treats
101+
# the physical TPM as a separate root of trust from the platform.
102+
#
103+
# Transport / store backends are currently fixed to VSOCK because that
104+
# is the only supported channel between an SEV-SNP CVM and a host-side
105+
# TPM endpoint; the `TpmTransport` and `SealedBlobStore` traits keep
106+
# the door open for future block-layer or KBS backends.
107+
vtpm-persist = ["vtpm", "vsock"]
108+
89109
[dev-dependencies]
90110
sha2 = { workspace = true, features = ["force-soft"] }
91111

kernel/src/cpu/x86/apic.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,9 @@ impl X86Apic {
157157
/// - `icr` - Value to write to the ICR register
158158
#[inline(always)]
159159
pub fn icr_write(&self, icr: u64) {
160-
self.regs()
161-
.icr_write(icr)
162-
.expect("Failed to write APIC.ICR");
160+
if let Err(e) = self.regs().icr_write(icr) {
161+
log::warn!("Failed to write APIC.ICR: {e:?}");
162+
}
163163
}
164164

165165
/// Checks whether an IRQ vector is currently in service

kernel/src/sev/snp_apic.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,18 @@ impl ApicAccess for GHCBApicAccessor {
6565
}
6666

6767
fn icr_write(&self, icr: u64) -> Result<(), SvsmError> {
68-
// The #HV IPI can only be used if restricted injection is supported.
69-
// Otherwise, the IPI must be sent via an X2APIC ICR write.
70-
if self.use_restr_inj() {
71-
current_ghcb().hv_ipi(icr)?;
72-
} else {
73-
self.apic_write(APIC_OFFSET_ICR, icr);
68+
// HV_IPI (GHCB exit 0x0015) is a base SEV-ES protocol feature and
69+
// does not require restricted injection. KVM rejects direct WRMSR to
70+
// the x2APIC ICR MSR (0x830) via the GHCB MSR protocol (error 2),
71+
// so HV_IPI is the only reliable path.
72+
match current_ghcb().hv_ipi(icr) {
73+
Ok(()) => Ok(()),
74+
Err(_) => {
75+
// Fallback: try direct MSR write if HV_IPI is unsupported
76+
self.apic_write(APIC_OFFSET_ICR, icr);
77+
Ok(())
78+
}
7479
}
75-
76-
Ok(())
7780
}
7881

7982
fn eoi(&self) {

kernel/src/svsm.rs

Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -72,16 +72,19 @@ 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)))]
75+
#[cfg(all(feature = "vtpm-persist", not(feature = "vsock"), not(test)))]
7676
use svsm::vtpm::MockTransport;
77-
#[cfg(all(feature = "vtpm", feature = "vsock", not(test)))]
77+
#[cfg(all(feature = "vtpm-persist", feature = "vsock", not(test)))]
7878
use svsm::vtpm::VsockTransport;
79-
#[cfg(all(feature = "vtpm", not(test)))]
80-
use svsm::vtpm::{SealedBlobStore, StaticBufStore, VtpmBootMode, vtpm_init_sealed};
81-
#[cfg(all(feature = "vtpm", feature = "vsock", not(test)))]
79+
#[cfg(all(feature = "vtpm", not(feature = "vtpm-persist"), not(test)))]
80+
use svsm::vtpm::vtpm_init;
81+
#[cfg(all(feature = "vtpm-persist", not(test)))]
82+
use svsm::vtpm::{SealedBlobStore, VsockHostStore, VtpmBootMode, vtpm_init_sealed};
83+
#[cfg(all(feature = "vtpm-persist", feature = "vsock", not(test)))]
8284
use svsm::vtpm::{VSOCK_HOST_CID, VSOCK_TPM_PORT};
8385

8486
use alloc::string::String;
87+
use alloc::vec::Vec;
8588
use release::COCONUT_VERSION;
8689

8790
#[cfg(feature = "attest")]
@@ -558,40 +561,62 @@ fn svsm_init(launch_info: &KernelLaunchInfo) {
558561
log::info!("attestation successful");
559562
}
560563

561-
#[cfg(all(feature = "vtpm", not(test)))]
564+
// virtio-mmio devices (including virtio-vsock used by VsockTransport)
565+
// must be initialized before vtpm sealed init, which opens the TPM
566+
// proxy channel.
567+
#[cfg(feature = "virtio-drivers")]
568+
initialize_virtio_mmio(&boot_params).expect("Failed to initialize virtio-mmio drivers");
569+
570+
// Mainline COCONUT-SVSM behaviour: ephemeral vTPM, no external
571+
// dependencies, fresh seeds on every cold boot. This is the path
572+
// upstream consumers get and the one preserved across the
573+
// `vtpm-persist` feature being off.
574+
#[cfg(all(feature = "vtpm", not(feature = "vtpm-persist"), not(test)))]
575+
{
576+
vtpm_init().expect("vtpm_init failed");
577+
log::info!("VTPM: ephemeral init complete");
578+
}
579+
580+
// Persistent vTPM path — opt-in via the `vtpm-persist` feature.
581+
// Drives the Provision/Recover seal cycle against an off-CVM TPM
582+
// endpoint (transport) and host-side blob store, both currently
583+
// reached over VSOCK. The host helper must be reachable: a
584+
// missing/unreachable endpoint is a hard error, NOT a silent
585+
// fallback to ephemeral — silently downgrading would void the
586+
// security model the seal cycle promises, which treats the
587+
// physical TPM as a separate root of trust (an attacker who can
588+
// break the VSOCK channel would otherwise harvest a fresh vTPM
589+
// identity by triggering the fallback).
590+
#[cfg(all(feature = "vtpm-persist", not(test)))]
562591
{
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");
592+
let store = VsockHostStore::new(
593+
VSOCK_HOST_CID,
594+
VsockHostStore::DEFAULT_LOAD_PORT,
595+
VsockHostStore::DEFAULT_SAVE_PORT,
596+
);
597+
598+
let blob_opt = store
599+
.load()
600+
.expect("VTPM: sealed-store load failed (host helper unreachable?)");
573601
let (boot_mode, blob_slice) = match blob_opt.as_deref() {
574602
Some(b) => (VtpmBootMode::Recover, Some(b)),
575603
None => (VtpmBootMode::Provision, None),
576604
};
577605

578606
let vm_id: [u8; 16] = [0u8; 16]; // TODO: derive from IGVM guest_context
579607

580-
#[cfg(feature = "vsock")]
581608
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-
};
587609

588-
let maybe_blob = vtpm_init_sealed(transport, boot_mode, vm_id, blob_slice)
589-
.expect("vTPM sealed init failed");
610+
// R1: vtpm_init_sealed now uses an out-param for the Vec<u8>; its
611+
// return type is the register-passable `Result<(), SvsmReqError>`.
612+
let mut maybe_blob: Option<Vec<u8>> = None;
613+
vtpm_init_sealed(transport, boot_mode, vm_id, blob_slice, &mut maybe_blob)
614+
.expect("VTPM: sealed init failed (host TPM endpoint unreachable?)");
590615

591616
if let Some(blob_bytes) = maybe_blob {
592617
store
593618
.save(&blob_bytes)
594-
.expect("SealedBlobStore::save failed");
619+
.expect("VTPM: sealed-store save failed");
595620
log::info!(
596621
"VTPM: SealedBlob ({} bytes) stored for next boot",
597622
blob_bytes.len()
@@ -603,9 +628,6 @@ fn svsm_init(launch_info: &KernelLaunchInfo) {
603628

604629
virt_log_usage();
605630

606-
#[cfg(feature = "virtio-drivers")]
607-
initialize_virtio_mmio(&boot_params).expect("Failed to initialize virtio-mmio drivers");
608-
609631
if let Err(e) = SVSM_PLATFORM.launch_fw(&boot_params) {
610632
panic!("Failed to launch FW: {e:?}");
611633
}

0 commit comments

Comments
 (0)