diff --git a/openhcl/underhill_attestation/src/lib.rs b/openhcl/underhill_attestation/src/lib.rs index de6b3cdace..96a85a5296 100644 --- a/openhcl/underhill_attestation/src/lib.rs +++ b/openhcl/underhill_attestation/src/lib.rs @@ -701,6 +701,7 @@ async fn get_derived_keys( }; // Handle various sources of Guest State Protection + let existing_unencrypted = !vmgs.is_encrypted() && !vmgs.was_provisioned_this_boot(); let is_gsp_by_id = key_protector_by_id.found_id && key_protector_by_id.inner.ported != 1; let is_gsp = key_protector.gsp[ingress_idx].gsp_length != 0; tracing::info!( @@ -714,7 +715,7 @@ async fn get_derived_keys( let mut requires_gsp_by_id = is_gsp_by_id; // Attempt GSP - let (gsp_response, no_gsp, requires_gsp) = { + let (gsp_response, gsp_available, no_gsp, requires_gsp) = { tracing::info!(CVM_ALLOWED, "attempting GSP"); let response = get_gsp_data(get, key_protector).await; @@ -728,8 +729,18 @@ async fn get_derived_keys( "GSP response" ); - let no_gsp = response.extended_status_flags.no_rpc_server() - || response.encrypted_gsp.length == 0 + let no_gsp_available = + response.extended_status_flags.no_rpc_server() || response.encrypted_gsp.length == 0; + + let no_gsp = no_gsp_available + // disable if auto and pre-existing guest state is not encrypted or + // encrypted using GspById to prevent encryption changes without + // explicit intent + || (matches!( + guest_state_encryption_policy, + GuestStateEncryptionPolicy::Auto + ) && (is_gsp_by_id || existing_unencrypted)) + // disable per encryption policy (first boot only, unless strict) || (matches!( guest_state_encryption_policy, GuestStateEncryptionPolicy::GspById | GuestStateEncryptionPolicy::None @@ -748,12 +759,12 @@ async fn get_derived_keys( requires_gsp_by_id = true; } - (response, no_gsp, requires_gsp) + (response, !no_gsp_available, no_gsp, requires_gsp) }; // Attempt GSP By Id protection if GSP is not available, when changing // schemes, or as requested - let (gsp_response_by_id, no_gsp_by_id) = if no_gsp || requires_gsp_by_id { + let (gsp_response_by_id, gsp_by_id_available, no_gsp_by_id) = if no_gsp || requires_gsp_by_id { tracing::info!(CVM_ALLOWED, "attempting GSP By Id"); let gsp_response_by_id = get @@ -761,7 +772,16 @@ async fn get_derived_keys( .await .map_err(GetDerivedKeysError::FetchGuestStateProtectionById)?; - let no_gsp_by_id = gsp_response_by_id.extended_status_flags.no_registry_file() + let no_gsp_by_id_available = gsp_response_by_id.extended_status_flags.no_registry_file(); + + let no_gsp_by_id = no_gsp_by_id_available + // disable if auto and pre-existing guest state is unencrypted + // to prevent encryption changes without explicit intent + || (matches!( + guest_state_encryption_policy, + GuestStateEncryptionPolicy::Auto + ) && existing_unencrypted) + // disable per encryption policy (first boot only, unless strict) || (matches!( guest_state_encryption_policy, GuestStateEncryptionPolicy::None @@ -771,9 +791,13 @@ async fn get_derived_keys( Err(GetDerivedKeysError::GspByIdRequiredButNotFound)? } - (gsp_response_by_id, no_gsp_by_id) + ( + gsp_response_by_id, + Some(!no_gsp_by_id_available), + no_gsp_by_id, + ) } else { - (GuestStateProtectionById::new_zeroed(), true) + (GuestStateProtectionById::new_zeroed(), None, true) }; // If sources of encryption used last are missing, attempt to unseal VMGS key with hardware key @@ -858,7 +882,9 @@ async fn get_derived_keys( tracing::info!( CVM_ALLOWED, kek = !no_kek, + gsp_available, gsp = !no_gsp, + gsp_by_id_available = ?gsp_by_id_available, gsp_by_id = !no_gsp_by_id, "Encryption sources" ); diff --git a/vm/devices/get/get_protocol/src/dps_json.rs b/vm/devices/get/get_protocol/src/dps_json.rs index dcf0e72fe6..66c33e7a8e 100644 --- a/vm/devices/get/get_protocol/src/dps_json.rs +++ b/vm/devices/get/get_protocol/src/dps_json.rs @@ -107,9 +107,10 @@ pub enum GuestStateLifetime { pub enum GuestStateEncryptionPolicy { /// Use the best encryption available, allowing fallback. /// - /// VMs will be created as or migrated to the best encryption available, + /// VMs will be created using the best encryption available, /// attempting GspKey, then GspById, and finally leaving the data - /// unencrypted if neither are available. + /// unencrypted if neither are available. VMs will not be migrated + /// to a different encryption method. #[default] Auto, /// Prefer (or require, if strict) no encryption. diff --git a/vm/vmgs/vmgs/src/vmgs_impl.rs b/vm/vmgs/vmgs/src/vmgs_impl.rs index fd732b5f75..f2d2b8e6e7 100644 --- a/vm/vmgs/vmgs/src/vmgs_impl.rs +++ b/vm/vmgs/vmgs/src/vmgs_impl.rs @@ -112,6 +112,7 @@ pub struct Vmgs { #[cfg_attr(feature = "inspect", inspect(iter_by_index))] encrypted_metadata_keys: [VmgsEncryptionKey; 2], reprovisioned: bool, + provisioned_this_boot: bool, #[cfg_attr(feature = "inspect", inspect(skip))] logger: Option>, @@ -245,7 +246,9 @@ impl Vmgs { logger: Option>, ) -> Result { let active_header = Self::format(&mut storage, VMGS_VERSION_3_0).await?; - Self::finish_open(storage, active_header, 0, logger).await + let mut vmgs = Self::finish_open(storage, active_header, 0, logger).await?; + vmgs.provisioned_this_boot = true; + Ok(vmgs) } async fn open_inner( @@ -367,6 +370,7 @@ impl Vmgs { metadata_key: VmgsDatastoreKey::new_zeroed(), encrypted_metadata_keys, reprovisioned, + provisioned_this_boot: false, #[cfg(feature = "inspect")] stats: Default::default(), @@ -1522,6 +1526,11 @@ impl Vmgs { self.encryption_algorithm != EncryptionAlgorithm::NONE } + /// Whether the VMGS file was provisioned during the most recent boot + pub fn was_provisioned_this_boot(&self) -> bool { + self.provisioned_this_boot + } + fn prepare_new_header(&self, file_table_fcb: &ResolvedFileControlBlock) -> VmgsHeader { VmgsHeader { signature: VMGS_SIGNATURE, @@ -1992,6 +2001,7 @@ pub mod save_restore { } }), reprovisioned, + provisioned_this_boot: false, logger, } } @@ -2020,6 +2030,7 @@ pub mod save_restore { encrypted_metadata_keys, logger: _, reprovisioned, + provisioned_this_boot: _, } = self; state::SavedVmgsState { diff --git a/vm/vmgs/vmgs_format/src/lib.rs b/vm/vmgs/vmgs_format/src/lib.rs index fae5d0c84a..7cea2ebea6 100644 --- a/vm/vmgs/vmgs_format/src/lib.rs +++ b/vm/vmgs/vmgs_format/src/lib.rs @@ -220,5 +220,5 @@ open_enum! { pub struct VmgsMarkers { pub reprovisioned: bool, #[bits(15)] - _reserved: u32, + _reserved: u16, } diff --git a/vm/vmgs/vmgs_resources/src/lib.rs b/vm/vmgs/vmgs_resources/src/lib.rs index f8ed256117..9053654f35 100644 --- a/vm/vmgs/vmgs_resources/src/lib.rs +++ b/vm/vmgs/vmgs_resources/src/lib.rs @@ -72,31 +72,17 @@ pub struct VmgsDisk { } /// Guest state encryption policy +/// +/// See detailed comments in `get_protocol` #[derive(MeshPayload, Debug, Clone, Copy)] pub enum GuestStateEncryptionPolicy { /// Use the best encryption available, allowing fallback. - /// - /// VMs will be created as or migrated to the best encryption available, - /// attempting GspKey, then GspById, and finally leaving the data - /// unencrypted if neither are available. Auto, /// Prefer (or require, if strict) no encryption. - /// - /// Do not encrypt the guest state unless it is already encrypted and - /// strict encryption policy is disabled. None(bool), /// Prefer (or require, if strict) GspById. - /// - /// This prevents a VM from being created as or migrated to GspKey even - /// if it is available. Exisiting GspKey encryption will be used unless - /// strict encryption policy is enabled. Fails if the data cannot be - /// encrypted. GspById(bool), /// Prefer (or require, if strict) GspKey. - /// - /// VMs will be created as or migrated to GspKey. GspById encryption will - /// be used if GspKey is unavailable unless strict encryption policy is - /// enabled. Fails if the data cannot be encrypted. GspKey(bool), }