diff --git a/Cargo.lock b/Cargo.lock index d90fa9eed896..68f996336ae1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22978,6 +22978,7 @@ dependencies = [ "base64 0.13.1", "config_types", "hkdf", + "linux_kernel_command_line", "pem", "rand 0.8.5", "sev", diff --git a/ic-os/bootloader/guestos_boot_args.template b/ic-os/bootloader/guestos_boot_args.template index 3fd7f052cf1c..bd58c8ecd07d 100644 --- a/ic-os/bootloader/guestos_boot_args.template +++ b/ic-os/bootloader/guestos_boot_args.template @@ -7,3 +7,5 @@ # useful for debug and policy development. BOOT_ARGS_A="root=/dev/disk/by-partuuid/7c0a626e-e5ea-e543-b5c5-300eb8304db7 console=ttyS0 console=tty0 nomodeset dfinity.system=A security=selinux selinux=1 enforcing=1 root_hash=ROOT_HASH" BOOT_ARGS_B="root=/dev/disk/by-partuuid/a78bc3a8-376c-054a-96e7-3904b915d0c5 console=ttyS0 console=tty0 nomodeset dfinity.system=B security=selinux selinux=1 enforcing=1 root_hash=ROOT_HASH" +BOOT_ARGS_TEE_A="root=/dev/disk/by-partuuid/7c0a626e-e5ea-e543-b5c5-300eb8304db7 console=ttyS0 console=tty0 nomodeset dfinity.system=A dfinity.tee=1 security=selinux selinux=1 enforcing=1 root_hash=ROOT_HASH" +BOOT_ARGS_TEE_B="root=/dev/disk/by-partuuid/a78bc3a8-376c-054a-96e7-3904b915d0c5 console=ttyS0 console=tty0 nomodeset dfinity.system=B dfinity.tee=1 security=selinux selinux=1 enforcing=1 root_hash=ROOT_HASH" diff --git a/ic-os/components/guestos/environment/90-sev-status.sh b/ic-os/components/guestos/environment/90-sev-status.sh index 5c2cfb3e0c50..7e25179528b2 100644 --- a/ic-os/components/guestos/environment/90-sev-status.sh +++ b/ic-os/components/guestos/environment/90-sev-status.sh @@ -1,16 +1,8 @@ #!/bin/bash -# Set SEV_ACTIVE environment variable based on systemd-detect-virt output. -# SEV detection involves querying the CPU using cpuid which goes through the HostOS. -# A malicious HostOS could intercept this call, but by querying the CPU only once early -# in the boot process and setting an environment variable, each service in the GuestOS -# will see the same value. Therefore, faking the initial cpuid is not an attack vector, -# as it would not allow selective manipulation of SEV status between services — it is -# equivalent of enabling/disabling SEV in the GuestOS VM config. - -# We only support SEV-SNP and no older SEV variants (sev, sev-es) -if [ "$(systemd-detect-virt --cvm)" = "sev-snp" ]; then - echo "SEV_ACTIVE=1" +# Set TEE_ENABLED environment variable based on kernel command line. +if grep -qE "(^|[[:space:]])dfinity\.tee(=|[[:space:]]|$)" /proc/cmdline; then + echo "TEE_ENABLED=1" else - echo "SEV_ACTIVE=0" + echo "TEE_ENABLED=0" fi diff --git a/ic-os/components/guestos/guest-upgrade-client/guest-upgrade-client.service b/ic-os/components/guestos/guest-upgrade-client/guest-upgrade-client.service index f6b7b8c8d1c8..84d37518210a 100644 --- a/ic-os/components/guestos/guest-upgrade-client/guest-upgrade-client.service +++ b/ic-os/components/guestos/guest-upgrade-client/guest-upgrade-client.service @@ -4,7 +4,7 @@ After=init-config.service network-online.target dev-sev\x2dguest.device Requires=init-config.service Wants=network-online.target dev-sev\x2dguest.device RequiresMountsFor=/var -ConditionEnvironment=SEV_ACTIVE=1 +ConditionKernelCommandLine=dfinity.tee [Service] Type=oneshot diff --git a/ic-os/components/monitoring/guestos/custom-metrics.sh b/ic-os/components/monitoring/guestos/custom-metrics.sh index d743aea7b737..e3954bb751e8 100644 --- a/ic-os/components/monitoring/guestos/custom-metrics.sh +++ b/ic-os/components/monitoring/guestos/custom-metrics.sh @@ -72,7 +72,7 @@ function update_tee_metrics() { "guestos_tee_active" \ "Indicates whether the virtual machine is running in a Trusted Execution Environment (1 = TEE enabled, 0 = not in TEE)" \ "gauge" - append_metric "$metric_family" "guestos_tee_active" "" "$SEV_ACTIVE" + append_metric "$metric_family" "guestos_tee_active" "" "$TEE_ENABLED" } function main() { diff --git a/ic-os/defs.bzl b/ic-os/defs.bzl index 95fa549f3045..852da3b5568c 100644 --- a/ic-os/defs.bzl +++ b/ic-os/defs.bzl @@ -292,7 +292,8 @@ def icos_build( # Create GuestLaunchMeasurements JSON for each CPU generation, vCPU count, and boot slot (for vcpu_flag in """ + vcpu_type_flags + """; do for vcpus in """ + vcpu_configs + """; do - for cmdline in "$$BOOT_ARGS_A" "$$BOOT_ARGS_B"; do + # Note: we only add TEE boot args to the launch measurements + for cmdline in "$$BOOT_ARGS_TEE_A" "$$BOOT_ARGS_TEE_B"; do hex=$$($(execpath //ic-os:sev-snp-measure) --mode snp --vcpus $$vcpus --ovmf "$(execpath //ic-os/components/ovmf:ovmf_sev)" $$vcpu_flag --append "$$cmdline" --initrd "$(location extracted_initrd.img)" --kernel "$(location extracted_vmlinuz)") # Convert hex string to decimal list, e.g. "abcd" -> 171\\n205 measurement=$$(echo -n "$$hex" | fold -w2 | sed "s/^/0x/" | xargs printf "%d\n") diff --git a/rs/ic_os/config/tool/src/guestos/bootstrap_ic_node.rs b/rs/ic_os/config/tool/src/guestos/bootstrap_ic_node.rs index ace6e05188b3..b5ac4d7ecd07 100644 --- a/rs/ic_os/config/tool/src/guestos/bootstrap_ic_node.rs +++ b/rs/ic_os/config/tool/src/guestos/bootstrap_ic_node.rs @@ -81,15 +81,20 @@ fn populate_nns_public_key_impl( /// Bootstrap IC Node from a bootstrap package #[cfg(target_os = "linux")] pub fn bootstrap_ic_node(bootstrap_dir: &Path, guestos_config: GuestOSConfig) -> Result<()> { - let is_sev_active = sev_guest::is_sev_active()?; - bootstrap_ic_node_impl(bootstrap_dir, Path::new("/"), guestos_config, is_sev_active) + let is_tee_enabled = sev_guest::is_tee_enabled()?; + bootstrap_ic_node_impl( + bootstrap_dir, + Path::new("/"), + guestos_config, + is_tee_enabled, + ) } fn bootstrap_ic_node_impl( bootstrap_dir: &Path, root: &Path, guestos_config: GuestOSConfig, - is_sev_active: bool, + is_tee_enabled: bool, ) -> Result<()> { let config_root = root.join(CONFIG_ROOT_PATH); let state_root = root.join(STATE_ROOT_PATH); @@ -106,7 +111,7 @@ fn bootstrap_ic_node_impl( &config_root, &state_root, guestos_config, - is_sev_active, + is_tee_enabled, ) .context("bootstrap failed")?; @@ -123,9 +128,9 @@ fn process_bootstrap( config_root: &Path, state_root: &Path, guestos_config: GuestOSConfig, - is_sev_active: bool, + is_tee_enabled: bool, ) -> Result<()> { - copy_bootstrap_files(bootstrap_dir, config_root, state_root, is_sev_active)?; + copy_bootstrap_files(bootstrap_dir, config_root, state_root, is_tee_enabled)?; // Write the node operator key if it was configured let node_op_key_dst = state_root.join(NODE_OPERATOR_KEY_PATH); @@ -180,7 +185,7 @@ fn copy_bootstrap_files( bootstrap_dir: &Path, config_root: &Path, state_root: &Path, - is_sev_active: bool, + is_tee_enabled: bool, ) -> Result<()> { // set up initial ssh authorized keys let ssh_keys_src = bootstrap_dir.join("accounts_ssh_authorized_keys"); @@ -191,7 +196,7 @@ fn copy_bootstrap_files( } // Restrict state injection on SEV production nodes - if is_sev_active { + if is_tee_enabled { #[cfg(not(feature = "dev"))] { println!("SEV is active - blocking state injection files for production variant"); @@ -361,7 +366,7 @@ mod tests { bootstrap_dir.path(), test_root.root_path(), guestos_config, - /*is_sev_active*/ false, + /*is_tee_enabled*/ false, ); assert!(result.is_ok()); @@ -416,7 +421,7 @@ mod tests { bootstrap_dir.path(), test_root.root_path(), guestos_config, - /*is_sev_active*/ false, + /*is_tee_enabled*/ false, ) .unwrap(); @@ -575,7 +580,7 @@ mod tests { bootstrap_dir.path(), test_root.root_path(), GuestOSConfig::default(), - /*is_sev_active*/ false, + /*is_tee_enabled*/ false, ) .unwrap(); @@ -600,7 +605,7 @@ mod tests { #[test] #[cfg(not(feature = "dev"))] - fn test_sev_active_prod_state_injection_blocked() { + fn test_tee_enabled_prod_state_injection_blocked() { // Create extracted directory structure let temp_dir = TempDir::new().unwrap(); let extracted_dir = temp_dir.path().join("extracted"); diff --git a/rs/ic_os/config/types/src/lib.rs b/rs/ic_os/config/types/src/lib.rs index 0469f01987e4..a1a341373fe5 100644 --- a/rs/ic_os/config/types/src/lib.rs +++ b/rs/ic_os/config/types/src/lib.rs @@ -153,8 +153,9 @@ pub struct ICOSSettings { /// SEV-SNP. /// /// IMPORTANT: This field only controls whether TEE is enabled in config. - /// In GuestOS code, check the $SEV_ACTIVE environment variable or use the `is_sev_active()` - /// wrapper from the `ic_sev` crate, as this cannot be faked by a malicious HostOS. + /// In GuestOS code, check the dfinity.tee kernel command line argument or the `TEE_ENABLED` + /// environment variable, or use the `is_tee_enabled()` wrapper from the `ic_sev` crate, as this + /// cannot be faked by a malicious HostOS. #[serde(default)] pub enable_trusted_execution_environment: bool, /// This ssh keys directory contains individual files named `admin`, `backup`, `readonly`, `recovery`. diff --git a/rs/ic_os/guest_upgrade/server/src/orchestrator.rs b/rs/ic_os/guest_upgrade/server/src/orchestrator.rs index fafe0c987f91..2ce4e6033c74 100644 --- a/rs/ic_os/guest_upgrade/server/src/orchestrator.rs +++ b/rs/ic_os/guest_upgrade/server/src/orchestrator.rs @@ -9,7 +9,7 @@ use { config_tool::{DEFAULT_GUESTOS_CONFIG_OBJECT_PATH, deserialize_config}, config_types::GuestOSConfig, sev::firmware::guest::Firmware, - sev_guest::is_sev_active, + sev_guest::is_tee_enabled, vsock_lib::LinuxVSockClient, }; @@ -20,12 +20,12 @@ pub fn new_disk_encryption_key_exchange_server_agent_for_orchestrator( handle: Handle, registry_client: Arc, ) -> Option { - let is_sev_active = is_sev_active().unwrap_or_else(|err| { + let is_tee_enabled = is_tee_enabled().unwrap_or_else(|err| { eprintln!("Failed to check if SEV is active, assuming it is not active: {err:?}"); false }); - if !is_sev_active { + if !is_tee_enabled { return None; } diff --git a/rs/ic_os/os_tools/guest_disk/src/main.rs b/rs/ic_os/os_tools/guest_disk/src/main.rs index bbe0cd46eea8..c3d54b03612b 100644 --- a/rs/ic_os/os_tools/guest_disk/src/main.rs +++ b/rs/ic_os/os_tools/guest_disk/src/main.rs @@ -57,7 +57,7 @@ fn main() -> Result<()> { run( args, &guestos_config, - sev_guest::is_sev_active().context("Failed to check if SEV is active")?, + sev_guest::is_tee_enabled().context("Failed to check if SEV is active")?, || { ::sev::firmware::guest::Firmware::open() .context("Failed to open /dev/sev-guest") @@ -74,7 +74,7 @@ fn main() -> Result<()> { fn run( args: Args, guestos_config: &GuestOSConfig, - is_sev_active: bool, + is_tee_enabled: bool, sev_firmware_factory: impl Fn() -> Result>, previous_key_path: &Path, generated_key_path: &Path, @@ -83,7 +83,7 @@ fn run( libcryptsetup_rs::set_log_callback::<()>(Some(cryptsetup_log), None); let metrics_file = metrics_file_path(metrics_dir, args.partition()); - let mut encryption: Box = if is_sev_active { + let mut encryption: Box = if is_tee_enabled { Box::new(SevDiskEncryption { sev_firmware: sev_firmware_factory().context("Failed to open SEV firmware")?, guest_vm_type: guestos_config.guest_vm_type, diff --git a/rs/ic_os/remote_attestation/server/src/main.rs b/rs/ic_os/remote_attestation/server/src/main.rs index 4a4a13f9b014..d0138d1a8d29 100644 --- a/rs/ic_os/remote_attestation/server/src/main.rs +++ b/rs/ic_os/remote_attestation/server/src/main.rs @@ -11,7 +11,7 @@ use remote_attestation_shared::proto::{AttestRequest, AttestResponse}; use sev::firmware::guest::Firmware; use sev_guest::attestation_package::generate_attestation_package; use sev_guest::firmware::SevGuestFirmware; -use sev_guest::is_sev_active; +use sev_guest::is_tee_enabled; use std::net::{IpAddr, SocketAddr}; use std::sync::{Arc, Mutex}; use tonic::transport::Server; @@ -86,7 +86,7 @@ impl RemoteAttestationService for RemoteAttestationServiceImpl { #[cfg(target_os = "linux")] #[tokio::main] async fn main() -> anyhow::Result<()> { - let service_impl = if is_sev_active()? { + let service_impl = if is_tee_enabled()? { let guestos_config: GuestOSConfig = deserialize_config(DEFAULT_GUESTOS_CONFIG_OBJECT_PATH) .context("Failed to read GuestOS config")?; diff --git a/rs/ic_os/sev/guest/BUILD.bazel b/rs/ic_os/sev/guest/BUILD.bazel index 93d607aa3b5f..123fd268468b 100644 --- a/rs/ic_os/sev/guest/BUILD.bazel +++ b/rs/ic_os/sev/guest/BUILD.bazel @@ -11,6 +11,7 @@ rust_library( crate_name = "sev_guest", deps = [ "//rs/ic_os/config/types:config_types", + "//rs/ic_os/linux_kernel_command_line", "//rs/ic_os/sev/attestation", "//rs/ic_os/sev/guest/firmware", "@crate_index//:anyhow", diff --git a/rs/ic_os/sev/guest/Cargo.toml b/rs/ic_os/sev/guest/Cargo.toml index cb5ed9751a3b..4ff01ff1258f 100644 --- a/rs/ic_os/sev/guest/Cargo.toml +++ b/rs/ic_os/sev/guest/Cargo.toml @@ -13,6 +13,7 @@ sha2 = { workspace = true } attestation = { path = "../attestation" } config_types = { path = "../../config/types" } +linux_kernel_command_line = { path = "../../linux_kernel_command_line" } sev_guest_firmware = { path = "firmware" } [dev-dependencies] diff --git a/rs/ic_os/sev/guest/src/lib.rs b/rs/ic_os/sev/guest/src/lib.rs index a7ab4e242278..f70599e3bea1 100644 --- a/rs/ic_os/sev/guest/src/lib.rs +++ b/rs/ic_os/sev/guest/src/lib.rs @@ -1,5 +1,6 @@ -#[cfg(target_os = "linux")] -use anyhow::{Context, Result, anyhow}; +use anyhow::{Context, Result}; +use linux_kernel_command_line::KernelCommandLine; +use std::str::FromStr; pub mod attestation_package; pub mod key_deriver; @@ -9,22 +10,25 @@ pub mod key_deriver; pub use sev_guest_firmware as firmware; /// Checks if SEV is active in the Guest Virtual Machine -#[cfg(target_os = "linux")] -pub fn is_sev_active() -> Result { - // We read the environment variable set by systemd instead of alternatives: - // - /dev/sev-guest: This device may not be available early in the boot process even when SEV is - // active. - // - cpuid: The call goes through the Host. If we invoked cpuid on every check, a malicious - // host could intercept the call and return different values thereby making some processes - // believe that the SEV is active and others believe it is not. - match std::env::var("SEV_ACTIVE") - .context("Could not read SEV_ACTIVE environment variable")? - .as_ref() - { - "1" => Ok(true), - "0" => Ok(false), - other => Err(anyhow!( - "SEV_ACTIVE was expected to be 0 or 1 but was: '{other}'" - )), +pub fn is_tee_enabled() -> Result { + let cmdline = std::fs::read_to_string("/proc/cmdline") + .context("Could not read kernel command line from /proc/cmdline")?; + is_tee_enabled_impl(&cmdline) +} + +fn is_tee_enabled_impl(cmdline: &str) -> Result { + let kernel_command_line = + KernelCommandLine::from_str(cmdline).context("Could not parse kernel command line")?; + Ok(kernel_command_line.get_argument("dfinity.tee").is_some()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_is_tee_enabled() { + assert!(is_tee_enabled_impl("root=/dev/disk/by-partuuid/7c0a626e-e5ea-e543-b5c5-300eb8304db7 console=ttyS0 console=tty0 nomodeset dfinity.system=A dfinity.tee=1 security=selinux selinux=1 enforcing=1 root_hash=abc1234").unwrap()); + assert!(!is_tee_enabled_impl("root=/dev/disk/by-partuuid/a78bc3a8-376c-054a-96e7-3904b915d0c5 console=ttyS0 console=tty0 nomodeset dfinity.system=B security=selinux selinux=1 enforcing=1 root_hash=12345").unwrap()); } } diff --git a/rs/orchestrator/src/registration.rs b/rs/orchestrator/src/registration.rs index f2465c1d2b97..7499335ca7c7 100644 --- a/rs/orchestrator/src/registration.rs +++ b/rs/orchestrator/src/registration.rs @@ -854,10 +854,10 @@ fn generate_node_registration_attestation( use der::asn1::OctetStringRef; use sev::firmware::guest::Firmware; use sev_guest::attestation_package::generate_attestation_package; - use sev_guest::is_sev_active; + use sev_guest::is_tee_enabled; // Check if SEV is active - let is_sev_active = match is_sev_active() { + let is_tee_enabled = match is_tee_enabled() { Ok(active) => active, Err(err) => { info!( @@ -868,7 +868,7 @@ fn generate_node_registration_attestation( } }; - if !is_sev_active { + if !is_tee_enabled { info!( log, "SEV is not active, skipping node registration attestation"