From 877befc1d724cf604d871aa1875cafa5830212a5 Mon Sep 17 00:00:00 2001 From: Claudio Carvalho Date: Tue, 1 Aug 2023 02:56:54 +0300 Subject: [PATCH 1/9] Makefile: Add verbose modes $ make V=1 or $ make V=2 These can be used to easily build targets in verbose mode. That can be helpful for debugging. Currently we support V=1 or V=2 (the biggest is the most verbose). Signed-off-by: Claudio Carvalho --- Makefile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Makefile b/Makefile index 990dd3d38..780abfbae 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,12 @@ else TARGET_PATH=debug endif +ifeq ($(V), 1) +CARGO_ARGS += -v +else ifeq ($(V), 2) +CARGO_ARGS += -vv +endif + STAGE2_ELF = "target/x86_64-unknown-none/${TARGET_PATH}/stage2" KERNEL_ELF = "target/x86_64-unknown-none/${TARGET_PATH}/svsm" TEST_KERNEL_ELF = target/x86_64-unknown-none/${TARGET_PATH}/svsm-test From 5c9706e5ffd13cfb7702f82f1960ede264a3e365 Mon Sep 17 00:00:00 2001 From: Claudio Carvalho Date: Wed, 2 Aug 2023 05:57:56 +0300 Subject: [PATCH 2/9] sev/ghcb: Add guest_request() and guest_ext_request() Both functions are used to send SNP_GUEST_REQUEST messages to the PSP, but the guest_ext_request() includes an extended request to the hypervisor. More information can be found in the AMD GHCB specification. Signed-off-by: Claudio Carvalho --- src/sev/ghcb.rs | 57 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/sev/ghcb.rs b/src/sev/ghcb.rs index 470c7331a..4f7f4c1c9 100644 --- a/src/sev/ghcb.rs +++ b/src/sev/ghcb.rs @@ -122,6 +122,8 @@ enum GHCBExitCode {} impl GHCBExitCode { pub const IOIO: u64 = 0x7b; pub const SNP_PSC: u64 = 0x8000_0010; + pub const GUEST_REQUEST: u64 = 0x8000_0011; + pub const GUEST_EXT_REQUEST: u64 = 0x8000_0012; pub const AP_CREATE: u64 = 0x80000013; pub const RUN_VMPL: u64 = 0x80000018; } @@ -476,6 +478,61 @@ impl GHCB { Ok(()) } + pub fn guest_request( + &mut self, + req_page: VirtAddr, + resp_page: VirtAddr, + ) -> Result<(), SvsmError> { + self.clear(); + + let info1: u64 = u64::from(virt_to_phys(req_page)); + let info2: u64 = u64::from(virt_to_phys(resp_page)); + + self.vmgexit(GHCBExitCode::GUEST_REQUEST, info1, info2)?; + + if !self.is_valid(OFF_SW_EXIT_INFO_2) { + return Err(GhcbError::VmgexitInvalid.into()); + } + + if self.sw_exit_info_2 != 0 { + return Err(GhcbError::VmgexitError(self.sw_exit_info_1, self.sw_exit_info_2).into()); + } + + Ok(()) + } + + pub fn guest_ext_request( + &mut self, + req_page: VirtAddr, + resp_page: VirtAddr, + data_pages: VirtAddr, + data_size: u64, + ) -> Result<(), SvsmError> { + self.clear(); + + let info1: u64 = u64::from(virt_to_phys(req_page)); + let info2: u64 = u64::from(virt_to_phys(resp_page)); + let rax: u64 = u64::from(virt_to_phys(data_pages)); + + self.set_rax(rax); + self.set_rbx(data_size); + + self.vmgexit(GHCBExitCode::GUEST_EXT_REQUEST, info1, info2)?; + + if !self.is_valid(OFF_SW_EXIT_INFO_2) { + return Err(GhcbError::VmgexitInvalid.into()); + } + + // On error, RBX and exit_info_2 are returned for proper error handling. + // For an extended request, if the buffer provided is too small, the hypervisor + // will return in RBX the number of contiguous pages required + if self.sw_exit_info_2 != 0 { + return Err(GhcbError::VmgexitError(self.rbx, self.sw_exit_info_2).into()); + } + + Ok(()) + } + pub fn run_vmpl(&mut self, vmpl: u64) -> Result<(), SvsmError> { self.clear(); self.vmgexit(GHCBExitCode::RUN_VMPL, vmpl, 0)?; From d658c554935dcd8c5ee513f4607e0a283983c7e7 Mon Sep 17 00:00:00 2001 From: Claudio Carvalho Date: Mon, 7 Aug 2023 01:51:08 +0300 Subject: [PATCH 3/9] sev/secrets_page: Export the VMPCK size Export the VMPCK size to be used in other crates. Signed-off-by: Claudio Carvalho --- src/sev/secrets_page.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sev/secrets_page.rs b/src/sev/secrets_page.rs index d88e97ff9..f8bf3a066 100644 --- a/src/sev/secrets_page.rs +++ b/src/sev/secrets_page.rs @@ -7,6 +7,8 @@ use crate::address::VirtAddr; use crate::sev::vmsa::VMPL_MAX; +pub const VMPCK_SIZE: usize = 32; + #[derive(Copy, Clone)] #[repr(C, packed)] pub struct SecretsPage { @@ -15,7 +17,7 @@ pub struct SecretsPage { pub fms: u32, reserved_00c: u32, pub gosvw: [u8; 16], - pub vmpck: [[u8; 32]; VMPL_MAX], + pub vmpck: [[u8; VMPCK_SIZE]; VMPL_MAX], reserved_0a0: [u8; 96], pub vmsa_tweak_bmp: [u64; 8], pub svsm_base: u64, From 2bdb8ce6904801faa67166be382ba50964ff1a51 Mon Sep 17 00:00:00 2001 From: Claudio Carvalho Date: Tue, 10 Oct 2023 17:05:35 +0300 Subject: [PATCH 4/9] crypto: Add SVSM kernel crypto API Add a generic interface for AES-256 GCM encryption and decryption. They are both required for requesting an attestation report. With this interface we should be able to keep the crypto code isolated in crates and also easily choose which crypto implementation should be compiled-in. Signed-off-by: Claudio Carvalho --- src/crypto/mod.rs | 77 +++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 2 files changed, 78 insertions(+) create mode 100644 src/crypto/mod.rs diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs new file mode 100644 index 000000000..9b4233bc8 --- /dev/null +++ b/src/crypto/mod.rs @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (C) 2023 IBM +// +// Authors: Claudio Carvalho + +//! SVSM kernel crypto API + +pub mod aead { + //! API for authentication encryption with associated data + + use crate::{protocols::errors::SvsmReqError, sev::secrets_page::VMPCK_SIZE}; + + // Message Header Format (AMD SEV-SNP spec. table 98) + + /// Authenticated tag size (128 bits) + pub const AUTHTAG_SIZE: usize = 16; + /// Initialization vector size (96 bits) + pub const IV_SIZE: usize = 12; + /// Key size + pub const KEY_SIZE: usize = VMPCK_SIZE; + + /// AES-256 GCM + pub trait Aes256GcmTrait { + /// Encrypt the provided buffer using AES-256 GCM + /// + /// # Arguments + /// + /// * `iv`: Initialization vector + /// * `key`: 256-bit key + /// * `aad`: Additional authenticated data + /// * `inbuf`: Cleartext buffer to be encrypted + /// * `outbuf`: Buffer to store the encrypted data, it must be large enough to also + /// hold the authenticated tag. + /// + /// # Returns + /// + /// * Success + /// * `usize`: Number of bytes written to `outbuf` + /// * Error + /// * [SvsmReqError] + fn encrypt( + iv: &[u8; IV_SIZE], + key: &[u8; KEY_SIZE], + aad: &[u8], + inbuf: &[u8], + outbuf: &mut [u8], + ) -> Result; + + /// Decrypt the provided buffer using AES-256 GCM + /// + /// # Returns + /// + /// * `iv`: Initialization vector + /// * `key`: 256-bit key + /// * `aad`: Additional authenticated data + /// * `inbuf`: Cleartext buffer to be decrypted, followed by the authenticated tag + /// * `outbuf`: Buffer to store the decrypted data + /// + /// # Returns + /// + /// * Success + /// * `usize`: Number of bytes written to `outbuf` + /// * Error + /// * [SvsmReqError] + fn decrypt( + iv: &[u8; IV_SIZE], + key: &[u8; KEY_SIZE], + aad: &[u8], + inbuf: &[u8], + outbuf: &mut [u8], + ) -> Result; + } + + /// Aes256Gcm type + pub struct Aes256Gcm; +} diff --git a/src/lib.rs b/src/lib.rs index 445420c19..271f65955 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ pub mod acpi; pub mod address; pub mod console; pub mod cpu; +pub mod crypto; pub mod debug; pub mod elf; pub mod error; From 4a25564cf80e602c216bcdd5f164537de9072e28 Mon Sep 17 00:00:00 2001 From: Claudio Carvalho Date: Tue, 10 Oct 2023 18:27:32 +0300 Subject: [PATCH 5/9] crypto: Add RustCrypto-based implementation Add a RustCrypto-based implementation for the SVSM Aes256GCM trait. Signed-off-by: Claudio Carvalho --- .cargo/config.toml | 2 + Cargo.lock | 203 ++++++++++++++++++++++++++++++++++----- Cargo.toml | 1 + src/crypto/mod.rs | 4 + src/crypto/rustcrypto.rs | 77 +++++++++++++++ 5 files changed, 263 insertions(+), 24 deletions(-) create mode 100644 src/crypto/rustcrypto.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 3bab4294e..0777ed2ed 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,6 +3,8 @@ target = "x86_64-unknown-none" rustflags = [ "-C", "force-frame-pointers", "-C", "linker-flavor=ld", + "--cfg", "aes_force_soft", + "--cfg", "polyval_force_soft", ] [target.x86_64-unknown-linux-gnu] diff --git a/Cargo.lock b/Cargo.lock index 1ce56a400..b946fbc64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,41 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -16,15 +51,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cfg-if" @@ -32,6 +67,44 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "cpufeatures" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "gdbstub" version = "0.6.6" @@ -56,6 +129,35 @@ dependencies = [ "num-traits", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "intrusive-collections" version = "0.9.6" @@ -65,11 +167,17 @@ dependencies = [ "memoffset", ] +[[package]] +name = "libc" +version = "0.2.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" + [[package]] name = "log" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "managed" @@ -88,50 +196,75 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "packit" version = "0.1.0" -source = "git+https://github.com/coconut-svsm/packit#fffebdc18a3f559f0a01425b17cf41b1c249fbe0" +source = "git+https://github.com/coconut-svsm/packit#540b471ee8da1d28fee8d9490888c84a48da04a8" dependencies = [ "zerocopy", ] [[package]] name = "paste" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b27ab7be369122c218afc2079489cdcb4b517c0a3fc386ff11e1fedfcc2b35" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "polyval" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] [[package]] name = "proc-macro2" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.29" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "svsm" version = "0.1.0" dependencies = [ - "bitflags 2.4.0", + "aes-gcm", + "bitflags 2.4.1", "gdbstub", "gdbstub_arch", "intrusive-collections", @@ -142,9 +275,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.109" +version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" dependencies = [ "proc-macro2", "quote", @@ -155,17 +288,39 @@ dependencies = [ name = "test" version = "0.1.0" +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-ident" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "zerocopy" -version = "0.6.1" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "332f188cc1bcf1fe1064b8c58d150f497e697f49774aa846f2dc949d9a25f236" +checksum = "96f8f25c15a0edc9b07eb66e7e6e97d124c0505435c382fde1ab7ceb188aa956" dependencies = [ "byteorder", "zerocopy-derive", @@ -173,9 +328,9 @@ dependencies = [ [[package]] name = "zerocopy-derive" -version = "0.3.2" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6505e6815af7de1746a08f69c69606bb45695a17149517680f3b2149713b19a3" +checksum = "855e0f6af9cd72b87d8a6c586f3cb583f5cdcc62c2c80869d8cd7e96fdf7ee20" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 568ceed01..6e65257b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ gdbstub_arch = { version = "0.2.4", optional = true } intrusive-collections = "0.9.6" log = { version = "0.4.17", features = ["max_level_info", "release_max_level_info"] } packit = { git = "https://github.com/coconut-svsm/packit", version = "0.1.0" } +aes-gcm = { version = "0.10.3", default-features = false, features = ["aes", "alloc"] } [target."x86_64-unknown-none".dev-dependencies] test = { version = "0.1.0", path = "test" } diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 9b4233bc8..b6efb0595 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -75,3 +75,7 @@ pub mod aead { /// Aes256Gcm type pub struct Aes256Gcm; } + +// Crypto implementations supported. Only one of them must be compiled-in. + +pub mod rustcrypto; diff --git a/src/crypto/rustcrypto.rs b/src/crypto/rustcrypto.rs new file mode 100644 index 000000000..559181432 --- /dev/null +++ b/src/crypto/rustcrypto.rs @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (C) 2023 IBM +// +// Authors: Claudio Carvalho + +//! RustCrypto implementation + +use aes_gcm::{ + aead::{Aead, Payload}, + Aes256Gcm, Key, KeyInit, Nonce, +}; + +use crate::{ + crypto::aead::{ + Aes256Gcm as CryptoAes256Gcm, Aes256GcmTrait as CryptoAes256GcmTrait, IV_SIZE, KEY_SIZE, + }, + protocols::errors::SvsmReqError, +}; + +#[repr(u64)] +#[derive(Clone, Copy, Debug, PartialEq)] +enum AesGcmOperation { + Encrypt = 0, + Decrypt = 1, +} + +fn aes_gcm_do( + operation: AesGcmOperation, + iv: &[u8; IV_SIZE], + key: &[u8; KEY_SIZE], + aad: &[u8], + inbuf: &[u8], + outbuf: &mut [u8], +) -> Result { + let payload = Payload { msg: inbuf, aad }; + + let aes_key = Key::::from_slice(key); + let gcm = Aes256Gcm::new(aes_key); + let nonce = Nonce::from_slice(iv); + + let result = if operation == AesGcmOperation::Encrypt { + gcm.encrypt(nonce, payload) + } else { + gcm.decrypt(nonce, payload) + }; + let buffer = result.map_err(|_| SvsmReqError::invalid_format())?; + + let outbuf = outbuf + .get_mut(..buffer.len()) + .ok_or_else(SvsmReqError::invalid_parameter)?; + outbuf.copy_from_slice(&buffer); + + Ok(buffer.len()) +} + +impl CryptoAes256GcmTrait for CryptoAes256Gcm { + fn encrypt( + iv: &[u8; IV_SIZE], + key: &[u8; KEY_SIZE], + aad: &[u8], + inbuf: &[u8], + outbuf: &mut [u8], + ) -> Result { + aes_gcm_do(AesGcmOperation::Encrypt, iv, key, aad, inbuf, outbuf) + } + + fn decrypt( + iv: &[u8; IV_SIZE], + key: &[u8; KEY_SIZE], + aad: &[u8], + inbuf: &[u8], + outbuf: &mut [u8], + ) -> Result { + aes_gcm_do(AesGcmOperation::Decrypt, iv, key, aad, inbuf, outbuf) + } +} From 9211b4fa1c6ff55fc692dab81b979606fb707fae Mon Sep 17 00:00:00 2001 From: Claudio Carvalho Date: Tue, 10 Oct 2023 20:39:45 +0300 Subject: [PATCH 6/9] greq: Add SnpGuestRequestMsg and SnpGuestRequestExtData These structures are used in the SNP_GUEST_REQUEST communication between the guest and the PSP; their implementation follow the AMD SEV-SNP specification, chapter 7. The SnpGuestRequestMsg is used to carry a SNP_GUEST_REQUEST command or response in the payload, which is encrypted using AES-256 GCM. This message can't be tampered with by the hypervisor because only the PSP and the guest have access to the key to decrypt the payload. An extended SNP_GUEST_REQUEST command also requests data from the hypervisor; in this case, the SnpGuestRequestExtData is also provided. The hypervisor will use it to store the requested data. Signed-off-by: Claudio Carvalho --- src/greq/mod.rs | 7 + src/greq/msg.rs | 609 ++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 3 files changed, 617 insertions(+) create mode 100644 src/greq/mod.rs create mode 100644 src/greq/msg.rs diff --git a/src/greq/mod.rs b/src/greq/mod.rs new file mode 100644 index 000000000..2184511be --- /dev/null +++ b/src/greq/mod.rs @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (C) 2023 IBM +// +// Authors: Claudio Carvalho + +pub mod msg; diff --git a/src/greq/msg.rs b/src/greq/msg.rs new file mode 100644 index 000000000..0dccebde5 --- /dev/null +++ b/src/greq/msg.rs @@ -0,0 +1,609 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (C) 2023 IBM +// +// Authors: Claudio Carvalho + +//! Message that carries an encrypted `SNP_GUEST_REQUEST` command in the payload + +extern crate alloc; + +use alloc::{ + alloc::{alloc_zeroed, Layout}, + boxed::Box, +}; +use core::{ + mem::size_of, + slice::{from_raw_parts, from_raw_parts_mut}, +}; + +use crate::{ + address::{Address, VirtAddr}, + cpu::percpu::this_cpu_mut, + crypto::aead::{Aes256Gcm, Aes256GcmTrait, AUTHTAG_SIZE, IV_SIZE}, + mm::virt_to_phys, + protocols::errors::SvsmReqError, + sev::{ghcb::PageStateChangeOp, secrets_page::VMPCK_SIZE}, + types::{PageSize, PAGE_SIZE}, +}; + +// Message Header Format (AMD SEV-SNP spec. table 98) + +/// Version of the message header +const HDR_VERSION: u8 = 1; +/// Version of the message payload +const MSG_VERSION: u8 = 1; + +/// AEAD Algorithm Encodings (AMD SEV-SNP spec. table 99) +#[derive(Clone, Copy, Debug, PartialEq)] +#[repr(u8)] +pub enum SnpGuestRequestAead { + Invalid = 0, + Aes256Gcm = 1, +} + +/// Message Type Encodings (AMD SEV-SNP spec. table 100) +#[derive(Clone, Copy, Debug, PartialEq)] +#[repr(u8)] +pub enum SnpGuestRequestMsgType { + Invalid = 0, + ReportRequest = 5, + ReportResponse = 6, +} + +impl TryFrom for SnpGuestRequestMsgType { + type Error = SvsmReqError; + + fn try_from(v: u8) -> Result { + match v { + x if x == SnpGuestRequestMsgType::Invalid as u8 => Ok(SnpGuestRequestMsgType::Invalid), + x if x == SnpGuestRequestMsgType::ReportRequest as u8 => { + Ok(SnpGuestRequestMsgType::ReportRequest) + } + x if x == SnpGuestRequestMsgType::ReportResponse as u8 => { + Ok(SnpGuestRequestMsgType::ReportResponse) + } + _ => Err(SvsmReqError::invalid_parameter()), + } + } +} + +/// Message header size +const MSG_HDR_SIZE: usize = size_of::(); +/// Message payload size +const MSG_PAYLOAD_SIZE: usize = PAGE_SIZE - MSG_HDR_SIZE; + +/// Maximum buffer size that the hypervisor takes to store the +/// SEV-SNP certificates +pub const SNP_GUEST_REQ_MAX_DATA_SIZE: usize = 4 * PAGE_SIZE; + +/// `SNP_GUEST_REQUEST` message format +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct SnpGuestRequestMsg { + hdr: SnpGuestRequestMsgHdr, + pld: [u8; MSG_PAYLOAD_SIZE], +} + +/// `SNP_GUEST_REQUEST` message header format +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct SnpGuestRequestMsgHdr { + /// Message authentication tag + authtag: [u8; 32], + /// The sequence number for this message + msg_seqno: u64, + /// Reserve. Must be zero. + rsvd1: [u8; 8], + /// The AEAD used to encrypt this message + algo: u8, + /// The version of the message header + hdr_version: u8, + /// The size of the message header in bytes + hdr_sz: u16, + /// The type of the payload + msg_type: u8, + /// The version of the payload + msg_version: u8, + /// The size of the payload in bytes + msg_sz: u16, + /// Reserved. Must be zero. + rsvd2: u32, + /// The ID of the VMPCK used to protect this message + msg_vmpck: u8, + /// Reserved. Must be zero. + rsvd3: [u8; 35], +} + +impl SnpGuestRequestMsgHdr { + /// Allocate a new [`SnpGuestRequestMsgHdr`] and initialize it + /// + /// # Panic + /// + /// * [`SnpGuestRequestMsgHdr`] size does not fit in a u16. + pub fn new(msg_sz: u16, msg_type: SnpGuestRequestMsgType, msg_seqno: u64) -> Self { + assert!(u16::try_from(MSG_HDR_SIZE).is_ok()); + + Self { + msg_seqno, + algo: SnpGuestRequestAead::Aes256Gcm as u8, + hdr_version: HDR_VERSION, + hdr_sz: MSG_HDR_SIZE as u16, + msg_type: msg_type as u8, + msg_version: MSG_VERSION, + msg_sz, + msg_vmpck: 0, + ..Default::default() + } + } + + /// Set the authenticated tag + fn set_authtag(&mut self, new_tag: &[u8]) -> Result<(), SvsmReqError> { + self.authtag + .get_mut(..new_tag.len()) + .ok_or_else(SvsmReqError::invalid_parameter)? + .copy_from_slice(new_tag); + Ok(()) + } + + /// Validate the [`SnpGuestRequestMsgHdr`] fields + fn validate( + &self, + msg_type: SnpGuestRequestMsgType, + msg_seqno: u64, + ) -> Result<(), SvsmReqError> { + let header_size = + u16::try_from(MSG_HDR_SIZE).map_err(|_| SvsmReqError::invalid_format())?; + if self.hdr_version != HDR_VERSION + || self.hdr_sz != header_size + || self.algo != SnpGuestRequestAead::Aes256Gcm as u8 + || self.msg_type != msg_type as u8 + || self.msg_vmpck != 0 + || self.msg_seqno != msg_seqno + { + return Err(SvsmReqError::invalid_format()); + } + Ok(()) + } + + /// Get a slice of the header fields used as additional authenticated data (AAD) + fn get_aad_slice(&self) -> &[u8] { + let self_gva = self as *const _ as *const u8; + let algo_gva = &self.algo as *const u8; + + let algo_offset = unsafe { algo_gva.offset_from(self_gva) } as usize; + + unsafe { from_raw_parts(algo_gva, MSG_HDR_SIZE - algo_offset) } + } + + /// Get [`SnpGuestRequestMsgHdr`] as a mutable slice reference + fn as_slice_mut(&mut self) -> &mut [u8] { + unsafe { from_raw_parts_mut(self as *mut _ as *mut u8, MSG_HDR_SIZE) } + } +} + +impl Default for SnpGuestRequestMsgHdr { + /// default() method implementation. We can't derive Default because + /// the field "rsvd3: [u8; 35]" conflicts with the Default trait, which + /// supports up to [T; 32]. + fn default() -> Self { + Self { + authtag: [0; 32], + msg_seqno: 0, + rsvd1: [0; 8], + algo: 0, + hdr_version: 0, + hdr_sz: 0, + msg_type: 0, + msg_version: 0, + msg_sz: 0, + rsvd2: 0, + msg_vmpck: 0, + rsvd3: [0; 35], + } + } +} + +impl SnpGuestRequestMsg { + /// Allocate the object in the heap without going through stack as + /// this is a large object + /// + /// # Panic + /// + /// * Memory allocated is not page aligned or Self does not + /// fit into a page + pub fn boxed_new() -> Result, SvsmReqError> { + let layout = Layout::new::(); + + // The GHCB spec says it has to fit in one page and be page aligned + assert!(layout.size() <= PAGE_SIZE); + + unsafe { + let addr = alloc_zeroed(layout); + if addr.is_null() { + return Err(SvsmReqError::invalid_request()); + } + + assert!(VirtAddr::from(addr).is_page_aligned()); + + let ptr = addr.cast::(); + Ok(Box::from_raw(ptr)) + } + } + + /// Clear the C-bit (memory encryption bit) for the Self page + /// + /// # Safety + /// + /// * The caller is responsible for setting the page back to encrypted + /// before the object is dropped. Shared pages should not be freed + /// (returned to the allocator) + pub fn set_shared(&mut self) -> Result<(), SvsmReqError> { + let vaddr = VirtAddr::from(self as *mut Self); + this_cpu_mut() + .get_pgtable() + .set_shared_4k(vaddr) + .map_err(|_| SvsmReqError::invalid_request())?; + + let paddr = virt_to_phys(vaddr); + this_cpu_mut() + .ghcb() + .page_state_change( + paddr, + paddr + PAGE_SIZE, + PageSize::Regular, + PageStateChangeOp::PscShared, + ) + .map_err(|_| SvsmReqError::invalid_request()) + } + + /// Set the C-bit (memory encryption bit) for the Self page + pub fn set_encrypted(&mut self) -> Result<(), SvsmReqError> { + let vaddr = VirtAddr::from(self as *mut Self); + this_cpu_mut() + .get_pgtable() + .set_encrypted_4k(vaddr) + .map_err(|_| SvsmReqError::invalid_request())?; + + let paddr = virt_to_phys(vaddr); + this_cpu_mut() + .ghcb() + .page_state_change( + paddr, + paddr + PAGE_SIZE, + PageSize::Regular, + PageStateChangeOp::PscPrivate, + ) + .map_err(|_| SvsmReqError::invalid_request()) + } + + /// Fill the [`SnpGuestRequestMsg`] fields with zeros + pub fn clear(&mut self) { + self.hdr.as_slice_mut().fill(0); + self.pld.fill(0); + } + + /// Encrypt the provided `SNP_GUEST_REQUEST` command and store the result in the actual message payload + /// + /// The command will be encrypted using AES-256 GCM and part of the message header will be + /// used as additional authenticated data (AAD). + /// + /// # Arguments + /// + /// * `msg_type`: Type of the command stored in the `command` buffer. + /// * `msg_seqno`: VMPL0 sequence number to be used in the message. The PSP will reject + /// subsequent messages when it detects that the sequence numbers are + /// out of sync. The sequence number is also used as initialization + /// vector (IV) in encryption. + /// * `vmpck0`: VMPCK0 key that will be used to encrypt the command. + /// * `command`: command slice to be encrypted. + /// + /// # Returns + /// + /// () on success and [`SvsmReqError`] on error. + /// + /// # Panic + /// + /// * The command length does not fit in a u16 + /// * The encrypted and the original command don't have the same size + pub fn encrypt_set( + &mut self, + msg_type: SnpGuestRequestMsgType, + msg_seqno: u64, + vmpck0: &[u8; VMPCK_SIZE], + command: &[u8], + ) -> Result<(), SvsmReqError> { + let payload_size_u16 = + u16::try_from(command.len()).map_err(|_| SvsmReqError::invalid_parameter())?; + + let mut msg_hdr = SnpGuestRequestMsgHdr::new(payload_size_u16, msg_type, msg_seqno); + let aad: &[u8] = msg_hdr.get_aad_slice(); + let iv: [u8; IV_SIZE] = build_iv(msg_seqno); + + self.pld.fill(0); + + // Encrypt the provided command and store the result in the message payload + let authtag_end: usize = Aes256Gcm::encrypt(&iv, vmpck0, aad, command, &mut self.pld)?; + + // In the Aes256Gcm encrypt API, the authtag is postfixed (comes after the encrypted payload) + let ciphertext_end: usize = authtag_end - AUTHTAG_SIZE; + let authtag = self + .pld + .get_mut(ciphertext_end..authtag_end) + .ok_or_else(SvsmReqError::invalid_request)?; + + // The command should have the same size when encrypted and decrypted + assert_eq!(command.len(), ciphertext_end); + + // Move the authtag to the message header + msg_hdr.set_authtag(authtag)?; + authtag.fill(0); + + self.hdr = msg_hdr; + + Ok(()) + } + + /// Decrypt the `SNP_GUEST_REQUEST` command stored in the message and store the decrypted command in + /// the provided `outbuf`. + /// + /// The command stored in the message payload is usually a response command received from the PSP. + /// It will be decrypted using AES-256 GCM and part of the message header will be used as + /// additional authenticated data (AAD). + /// + /// # Arguments + /// + /// * `msg_type`: Type of the command stored in the message payload + /// * `msg_seqno`: VMPL0 sequence number that was used in the message. + /// * `vmpck0`: VMPCK0 key, it will be used to decrypt the message + /// * `outbuf`: buffer that will be used to store the decrypted message payload + /// + /// # Returns + /// + /// * Success + /// * usize: Number of bytes written to `outbuf` + /// * Error + /// * [`SvsmReqError`] + pub fn decrypt_get( + &mut self, + msg_type: SnpGuestRequestMsgType, + msg_seqno: u64, + vmpck0: &[u8; VMPCK_SIZE], + outbuf: &mut [u8], + ) -> Result { + self.hdr.validate(msg_type, msg_seqno)?; + + let iv: [u8; IV_SIZE] = build_iv(msg_seqno); + let aad: &[u8] = self.hdr.get_aad_slice(); + + // In the Aes256Gcm decrypt API, the authtag must be provided postfix in the inbuf + let ciphertext_end = usize::from(self.hdr.msg_sz); + let tag_end: usize = ciphertext_end + AUTHTAG_SIZE; + + // The message payload must be large enough to hold the ciphertext and + // the authentication tag. + let hdr_tag = self + .hdr + .authtag + .get(..AUTHTAG_SIZE) + .ok_or_else(SvsmReqError::invalid_request)?; + let pld_tag = self + .pld + .get_mut(ciphertext_end..tag_end) + .ok_or_else(SvsmReqError::invalid_request)?; + pld_tag.copy_from_slice(hdr_tag); + + // Payload with postfixed authtag + let inbuf = self + .pld + .get(..tag_end) + .ok_or_else(SvsmReqError::invalid_request)?; + + let outbuf_len: usize = Aes256Gcm::decrypt(&iv, vmpck0, aad, inbuf, outbuf)?; + + Ok(outbuf_len) + } +} + +/// Build the initialization vector for AES-256 GCM +fn build_iv(msg_seqno: u64) -> [u8; IV_SIZE] { + const U64_SIZE: usize = size_of::(); + let mut iv = [0u8; IV_SIZE]; + + iv[..U64_SIZE].copy_from_slice(&msg_seqno.to_ne_bytes()); + iv +} + +/// Set to encrypted all the 4k pages of a memory range +fn set_encrypted_region_4k(start: VirtAddr, end: VirtAddr) -> Result<(), SvsmReqError> { + for addr in (start.bits()..end.bits()) + .step_by(PAGE_SIZE) + .map(VirtAddr::from) + { + this_cpu_mut() + .get_pgtable() + .set_encrypted_4k(addr) + .map_err(|_| SvsmReqError::invalid_request())?; + + let paddr = virt_to_phys(addr); + this_cpu_mut() + .ghcb() + .page_state_change( + paddr, + paddr + PAGE_SIZE, + PageSize::Regular, + PageStateChangeOp::PscPrivate, + ) + .map_err(|_| SvsmReqError::invalid_request())?; + } + Ok(()) +} + +/// Set to shared all the 4k pages of a memory range +fn set_shared_region_4k(start: VirtAddr, end: VirtAddr) -> Result<(), SvsmReqError> { + for addr in (start.bits()..end.bits()) + .step_by(PAGE_SIZE) + .map(VirtAddr::from) + { + this_cpu_mut() + .get_pgtable() + .set_shared_4k(addr) + .map_err(|_| SvsmReqError::invalid_request())?; + + let paddr = virt_to_phys(addr); + this_cpu_mut() + .ghcb() + .page_state_change( + paddr, + paddr + PAGE_SIZE, + PageSize::Regular, + PageStateChangeOp::PscShared, + ) + .map_err(|_| SvsmReqError::invalid_request())?; + } + Ok(()) +} + +/// Data page(s) the hypervisor will use to store certificate data in +/// an extended `SNP_GUEST_REQUEST` +#[repr(C, packed)] +#[derive(Debug)] +pub struct SnpGuestRequestExtData { + /// According to the GHCB spec, the data page(s) must be contiguous pages if + /// supplying more than one page and all certificate pages must be + /// assigned to the hypervisor (shared). + data: [u8; SNP_GUEST_REQ_MAX_DATA_SIZE], +} + +impl SnpGuestRequestExtData { + /// Allocate the object in the heap without going through stack as + /// this is a large object + pub fn boxed_new() -> Result, SvsmReqError> { + let layout = Layout::new::(); + unsafe { + let addr = alloc_zeroed(layout); + if addr.is_null() { + return Err(SvsmReqError::invalid_request()); + } + assert!(VirtAddr::from(addr).is_page_aligned()); + + let ptr = addr.cast::(); + Ok(Box::from_raw(ptr)) + } + } + + /// Clear the C-bit (memory encryption bit) for the Self pages + /// + /// # Safety + /// + /// * The caller is responsible for setting the page back to encrypted + /// before the object is dropped. Shared pages should not be freed + /// (returned to the allocator) + pub fn set_shared(&mut self) -> Result<(), SvsmReqError> { + const EXT_DATA_SIZE: usize = size_of::(); + + let start = VirtAddr::from(self as *mut Self); + let end = VirtAddr::from(start.bits() + EXT_DATA_SIZE); + set_shared_region_4k(start, end) + } + + /// Set the C-bit (memory encryption bit) for the Self pages + pub fn set_encrypted(&mut self) -> Result<(), SvsmReqError> { + const EXT_DATA_SIZE: usize = size_of::(); + + let start = VirtAddr::from(self as *mut Self); + let end = VirtAddr::from(start.bits() + EXT_DATA_SIZE); + set_encrypted_region_4k(start, end) + } + + /// Clear the first `n` bytes from data + pub fn nclear(&mut self, n: usize) -> Result<(), SvsmReqError> { + self.data + .get_mut(..n) + .ok_or_else(SvsmReqError::invalid_parameter)? + .fill(0); + Ok(()) + } + + /// Fill up the `outbuf` slice provided with bytes from data + pub fn copy_to_slice(&self, outbuf: &mut [u8]) -> Result<(), SvsmReqError> { + let data = self + .data + .get(..outbuf.len()) + .ok_or_else(SvsmReqError::invalid_parameter)?; + outbuf.copy_from_slice(data); + Ok(()) + } + + /// Check if the first `n` bytes from data are zeroed + pub fn is_nclear(&self, n: usize) -> Result { + let data = self + .data + .get(..n) + .ok_or_else(SvsmReqError::invalid_parameter)?; + Ok(data.iter().all(|e| *e == 0)) + } +} + +#[cfg(test)] +mod tests { + use crate::{ + greq::msg::{ + SnpGuestRequestMsg, SnpGuestRequestMsgHdr, SnpGuestRequestMsgType, MSG_HDR_SIZE, + MSG_PAYLOAD_SIZE, + }, + sev::secrets_page::VMPCK_SIZE, + }; + + #[test] + fn u16_from_guest_msg_hdr_size() { + assert!(u16::try_from(MSG_HDR_SIZE).is_ok()); + } + + #[test] + fn aad_size() { + let hdr = SnpGuestRequestMsgHdr::default(); + let aad = hdr.get_aad_slice(); + + const HDR_ALGO_OFFSET: usize = 48; + + assert_eq!(aad.len(), MSG_HDR_SIZE - HDR_ALGO_OFFSET); + } + + #[test] + fn encrypt_decrypt_payload() { + let mut msg = SnpGuestRequestMsg { + hdr: SnpGuestRequestMsgHdr::default(), + pld: [0; MSG_PAYLOAD_SIZE], + }; + + const PLAINTEXT: &[u8] = b"request-to-be-encrypted"; + let vmpck0 = [5u8; VMPCK_SIZE]; + let vmpck0_seqno: u64 = 1; + + let result = msg.encrypt_set( + SnpGuestRequestMsgType::ReportRequest, + vmpck0_seqno, + &vmpck0, + PLAINTEXT, + ); + + assert!(result.is_ok()); + + let mut outbuf = [0u8; PLAINTEXT.len()]; + + let result = msg.decrypt_get( + SnpGuestRequestMsgType::ReportRequest, + vmpck0_seqno, + &vmpck0, + &mut outbuf, + ); + + assert!(result.is_ok()); + + let outbuf_len = result.unwrap(); + assert_eq!(outbuf_len, PLAINTEXT.len()); + + assert_eq!(outbuf, PLAINTEXT); + } +} diff --git a/src/lib.rs b/src/lib.rs index 271f65955..164a64e30 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,7 @@ pub mod error; pub mod fs; pub mod fw_cfg; pub mod fw_meta; +pub mod greq; pub mod io; pub mod kernel_launch; pub mod locking; From c491656db90e6198d330815c6839df6ea0446c24 Mon Sep 17 00:00:00 2001 From: Claudio Carvalho Date: Wed, 2 Aug 2023 06:55:01 +0300 Subject: [PATCH 7/9] greq: Add SnpGuestRequestDriver Add a driver to send SNP_GUEST_REQUEST commands to the PSP. The command can be any of the request or response command types defined in the SEV-SNP spec, regardless if it's a regular or an extended command. The send_regular_guest_request() and send_extended_guest_request() functions can be used to send regular and extended commands, respectively. guest_request_driver_init() is used to initialize the static driver instance. Signed-off-by: Claudio Carvalho --- src/greq/driver.rs | 403 ++++++++++++++++++++++++++++++++++++++++ src/greq/mod.rs | 1 + src/sev/secrets_page.rs | 17 ++ src/svsm.rs | 3 + src/utils/util.rs | 8 + 5 files changed, 432 insertions(+) create mode 100644 src/greq/driver.rs diff --git a/src/greq/driver.rs b/src/greq/driver.rs new file mode 100644 index 000000000..fad3edfce --- /dev/null +++ b/src/greq/driver.rs @@ -0,0 +1,403 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (C) 2023 IBM +// +// Authors: Claudio Carvalho + +//! Driver to send `SNP_GUEST_REQUEST` commands to the PSP. It can be any of the +//! request or response command types defined in the SEV-SNP spec, regardless if it's +//! a regular or an extended command. + +extern crate alloc; + +use alloc::boxed::Box; +use core::{cell::OnceCell, mem::size_of}; + +use crate::{ + address::VirtAddr, + cpu::percpu::this_cpu_mut, + error::SvsmError, + greq::msg::{SnpGuestRequestExtData, SnpGuestRequestMsg, SnpGuestRequestMsgType}, + locking::SpinLock, + protocols::errors::{SvsmReqError, SvsmResultCode}, + sev::{ + ghcb::GhcbError, + secrets_page::{disable_vmpck0, get_vmpck0, is_vmpck0_clear, VMPCK_SIZE}, + }, + types::PAGE_SHIFT, + BIT, +}; + +/// Global `SNP_GUEST_REQUEST` driver instance +static GREQ_DRIVER: SpinLock> = SpinLock::new(OnceCell::new()); + +// Hypervisor error codes + +/// Buffer provided is too small +const SNP_GUEST_REQ_INVALID_LEN: u64 = BIT!(32); +/// Hypervisor busy, try again +const SNP_GUEST_REQ_ERR_BUSY: u64 = BIT!(33); + +/// Class of the `SNP_GUEST_REQUEST` command: Regular or Extended +#[derive(Clone, Copy, Debug, PartialEq)] +#[repr(u8)] +enum SnpGuestRequestClass { + Regular = 0, + Extended = 1, +} + +/// `SNP_GUEST_REQUEST` driver +#[derive(Debug)] +struct SnpGuestRequestDriver { + /// Shared page used for the `SNP_GUEST_REQUEST` request + request: Box, + /// Shared page used for the `SNP_GUEST_REQUEST` response + response: Box, + /// Encrypted page where we perform crypto operations + staging: Box, + /// Extended data buffer that will be provided to the hypervisor + /// to store the SEV-SNP certificates + ext_data: Box, + /// Extended data size (`certs` size) provided by the user in [`get_extended_report()`]. + /// It will be provided to the hypervisor. + user_extdata_size: usize, + /// Each `SNP_GUEST_REQUEST` message contains a sequence number per VMPCK. + /// The sequence number is incremented with each message sent. Messages + /// sent by the guest to the PSP and by the PSP to the guest must be + /// delivered in order. If not, the PSP will reject subsequent messages + /// by the guest when it detects that the sequence numbers are out of sync. + /// + /// NOTE: If the vmpl field of a `SNP_GUEST_REQUEST` message is set to VMPL0, + /// then it must contain the VMPL0 sequence number and be protected (encrypted) + /// with the VMPCK0 key; additionally, if this message fails, the VMPCK0 key + /// must be disabled. The same idea applies to the other VMPL levels. + /// + /// The SVSM needs to support only VMPL0 `SNP_GUEST_REQUEST` commands because + /// other layers in the software stack (e.g. OVMF and guest kernel) can send + /// non-VMPL0 commands directly to PSP. Therefore, the SVSM needs to maintain + /// the sequence number and the VMPCK only for VMPL0. + vmpck0_seqno: u64, +} + +impl Drop for SnpGuestRequestDriver { + fn drop(&mut self) { + if self.request.set_encrypted().is_err() { + let new_req = + SnpGuestRequestMsg::boxed_new().expect("GREQ: failed to allocate request"); + let old_req = core::mem::replace(&mut self.request, new_req); + log::error!("GREQ: request: failed to set page to encrypted. Memory leak!"); + Box::leak(old_req); + } + if self.response.set_encrypted().is_err() { + let new_resp = + SnpGuestRequestMsg::boxed_new().expect("GREQ: failed to allocate response"); + let old_resp = core::mem::replace(&mut self.response, new_resp); + log::error!("GREQ: response: failed to set page to encrypted. Memory leak!"); + Box::leak(old_resp); + } + if self.ext_data.set_encrypted().is_err() { + let new_data = + SnpGuestRequestExtData::boxed_new().expect("GREQ: failed to allocate ext_data"); + let old_data = core::mem::replace(&mut self.ext_data, new_data); + log::error!("GREQ: ext_data: failed to set pages to encrypted. Memory leak!"); + Box::leak(old_data); + } + } +} + +impl SnpGuestRequestDriver { + /// Create a new [`SnpGuestRequestDriver`] + pub fn new() -> Result { + let request = SnpGuestRequestMsg::boxed_new()?; + let response = SnpGuestRequestMsg::boxed_new()?; + let staging = SnpGuestRequestMsg::boxed_new()?; + let ext_data = SnpGuestRequestExtData::boxed_new()?; + + let mut driver = Self { + request, + response, + staging, + ext_data, + user_extdata_size: size_of::(), + vmpck0_seqno: 0, + }; + + driver.request.set_shared()?; + driver.response.set_shared()?; + driver.ext_data.set_shared()?; + + Ok(driver) + } + + /// Get the last VMPCK0 sequence number accounted + fn seqno_last_used(&self) -> u64 { + self.vmpck0_seqno + } + + /// Increase the VMPCK0 sequence number by two. In order to keep the + /// sequence number in-sync with the PSP, this is called only when the + /// `SNP_GUEST_REQUEST` response is received. + fn seqno_add_two(&mut self) { + self.vmpck0_seqno += 2; + } + + /// Set the user_extdata_size to `n` and clear the first `n` bytes from `ext_data` + pub fn set_user_extdata_size(&mut self, n: usize) -> Result<(), SvsmReqError> { + // At least one page + if (n >> PAGE_SHIFT) == 0 { + return Err(SvsmReqError::invalid_parameter()); + } + self.ext_data.nclear(n)?; + self.user_extdata_size = n; + + Ok(()) + } + + /// Call the GHCB layer to send the encrypted SNP_GUEST_REQUEST message + /// to the PSP. + fn send(&mut self, req_class: SnpGuestRequestClass) -> Result<(), SvsmReqError> { + self.response.clear(); + + let req_page = VirtAddr::from(&mut *self.request as *mut SnpGuestRequestMsg); + let resp_page = VirtAddr::from(&mut *self.response as *mut SnpGuestRequestMsg); + let data_pages = VirtAddr::from(&mut *self.ext_data as *mut SnpGuestRequestExtData); + + if req_class == SnpGuestRequestClass::Extended { + let num_user_pages = (self.user_extdata_size >> PAGE_SHIFT) as u64; + this_cpu_mut().ghcb().guest_ext_request( + req_page, + resp_page, + data_pages, + num_user_pages, + )?; + } else { + this_cpu_mut().ghcb().guest_request(req_page, resp_page)?; + } + + self.seqno_add_two(); + + Ok(()) + } + + // Encrypt the request message from encrypted memory + fn encrypt_request( + &mut self, + msg_type: SnpGuestRequestMsgType, + msg_seqno: u64, + buffer: &mut [u8], + command_len: usize, + ) -> Result<(), SvsmReqError> { + // VMPL0 `SNP_GUEST_REQUEST` commands are encrypted with the VMPCK0 key + let vmpck0: [u8; VMPCK_SIZE] = get_vmpck0(); + + let inbuf = buffer + .get(..command_len) + .ok_or_else(SvsmReqError::invalid_parameter)?; + + // For security reasons, encrypt the message in protected memory (staging) + // and then copy the result to shared memory (request) + self.staging + .encrypt_set(msg_type, msg_seqno, &vmpck0, inbuf)?; + *self.request = *self.staging; + Ok(()) + } + + // Decrypt the response message from encrypted memory + fn decrypt_response( + &mut self, + msg_seqno: u64, + msg_type: SnpGuestRequestMsgType, + buffer: &mut [u8], + ) -> Result { + let vmpck0: [u8; VMPCK_SIZE] = get_vmpck0(); + + // For security reasons, decrypt the message in protected memory (staging) + *self.staging = *self.response; + let result = self + .staging + .decrypt_get(msg_type, msg_seqno, &vmpck0, buffer); + + if let Err(e) = result { + match e { + // The buffer provided is too small to store the unwrapped response. + // There is no need to clear the VMPCK0, just report it as invalid parameter. + SvsmReqError::RequestError(SvsmResultCode::INVALID_PARAMETER) => (), + _ => disable_vmpck0(), + } + } + + result + } + + /// Send the provided VMPL0 `SNP_GUEST_REQUEST` command to the PSP. + /// + /// The command will be encrypted using AES-256 GCM. + /// + /// # Arguments + /// + /// * `req_class`: whether this is a regular or extended `SNP_GUEST_REQUEST` command + /// * `msg_type`: type of the command stored in `buffer`, e.g. [`SNP_MSG_REPORT_REQ`] + /// * `buffer`: buffer with the `SNP_GUEST_REQUEST` command to be sent. + /// The same buffer will also be used to store the response. + /// * `command_len`: Size (in bytes) of the command stored in `buffer` + /// + /// # Returns + /// + /// * Success: + /// * `usize`: Size (in bytes) of the response stored in `buffer` + /// * Error: + /// * [`SvsmReqError`] + fn send_request( + &mut self, + req_class: SnpGuestRequestClass, + msg_type: SnpGuestRequestMsgType, + buffer: &mut [u8], + command_len: usize, + ) -> Result { + if is_vmpck0_clear() { + return Err(SvsmReqError::invalid_request()); + } + + // Message sequence number overflow, the driver will not able + // to send subsequent `SNP_GUEST_REQUEST` messages to the PSP. + // The sequence number is restored only when the guest is rebooted. + let Some(msg_seqno) = self.seqno_last_used().checked_add(1) else { + log::error!("SNP_GUEST_REQUEST: sequence number overflow"); + disable_vmpck0(); + return Err(SvsmReqError::invalid_request()); + }; + + self.encrypt_request(msg_type, msg_seqno, buffer, command_len)?; + + if let Err(e) = self.send(req_class) { + if let SvsmReqError::FatalError(SvsmError::Ghcb(GhcbError::VmgexitError(_rbx, info2))) = + e + { + // For some reason the hypervisor did not forward the request to the PSP. + // + // Because the message sequence number is used as part of the AES-GCM IV, it is important that the + // guest retry the request before allowing another request to be performed so that the IV cannot be + // reused on a new message payload. + match info2 & 0xffff_ffff_0000_0000u64 { + // The certificate buffer provided is too small. + SNP_GUEST_REQ_INVALID_LEN => { + if req_class == SnpGuestRequestClass::Extended { + if let Err(e1) = self.send(SnpGuestRequestClass::Regular) { + log::error!( + "SNP_GUEST_REQ_INVALID_LEN. Aborting, request resend failed" + ); + disable_vmpck0(); + return Err(e1); + } + return Err(e); + } else { + // We sent a regular SNP_GUEST_REQUEST, but the hypervisor returned + // an error code that is exclusive for extended SNP_GUEST_REQUEST + disable_vmpck0(); + return Err(SvsmReqError::invalid_request()); + } + } + // The hypervisor is busy. + SNP_GUEST_REQ_ERR_BUSY => { + if let Err(e2) = self.send(req_class) { + log::error!("SNP_GUEST_REQ_ERR_BUSY. Aborting, request resend failed"); + disable_vmpck0(); + return Err(e2); + } + // ... request resend worked, continue normally. + } + // Failed for unknown reason. Status codes can be found in + // the AMD SEV-SNP spec or in the linux kernel include/uapi/linux/psp-sev.h + _ => { + log::error!("SNP_GUEST_REQUEST failed, unknown error code={}\n", info2); + disable_vmpck0(); + return Err(e); + } + } + } + } + + let msg_seqno = self.seqno_last_used(); + let resp_msg_type = SnpGuestRequestMsgType::try_from(msg_type as u8 + 1)?; + + self.decrypt_response(msg_seqno, resp_msg_type, buffer) + } + + /// Send the provided regular `SNP_GUEST_REQUEST` command to the PSP + pub fn send_regular_guest_request( + &mut self, + msg_type: SnpGuestRequestMsgType, + buffer: &mut [u8], + command_len: usize, + ) -> Result { + self.send_request(SnpGuestRequestClass::Regular, msg_type, buffer, command_len) + } + + /// Send the provided extended `SNP_GUEST_REQUEST` command to the PSP + pub fn send_extended_guest_request( + &mut self, + msg_type: SnpGuestRequestMsgType, + buffer: &mut [u8], + command_len: usize, + certs: &mut [u8], + ) -> Result { + self.set_user_extdata_size(certs.len())?; + + let outbuf_len: usize = self.send_request( + SnpGuestRequestClass::Extended, + msg_type, + buffer, + command_len, + )?; + + // The SEV-SNP certificates can be used to verify the attestation report. At this point, a zeroed + // ext_data buffer indicates that the certificates were not imported. + // The VM owner can import them from the host using the virtee/snphost project + if self.ext_data.is_nclear(certs.len())? { + log::warn!("SEV-SNP certificates not found. Make sure they were loaded from the host."); + } else { + self.ext_data.copy_to_slice(certs)?; + } + + Ok(outbuf_len) + } +} + +/// Initialize the global `SnpGuestRequestDriver` +/// +/// # Panics +/// +/// This function panics if we fail to initialize any of the `SnpGuestRequestDriver` fields. +pub fn guest_request_driver_init() { + let cell = GREQ_DRIVER.lock(); + let _ = cell.get_or_init(|| { + SnpGuestRequestDriver::new().expect("SnpGuestRequestDriver failed to initialize") + }); +} + +/// Send the provided regular `SNP_GUEST_REQUEST` command to the PSP. +/// Further details can be found in the `SnpGuestRequestDriver.send_request()` documentation. +pub fn send_regular_guest_request( + msg_type: SnpGuestRequestMsgType, + buffer: &mut [u8], + request_len: usize, +) -> Result { + let mut cell = GREQ_DRIVER.lock(); + let driver: &mut SnpGuestRequestDriver = + cell.get_mut().ok_or_else(SvsmReqError::invalid_request)?; + driver.send_regular_guest_request(msg_type, buffer, request_len) +} + +/// Send the provided extended `SNP_GUEST_REQUEST` command to the PSP +/// Further details can be found in the `SnpGuestRequestDriver.send_request()` documentation. +pub fn send_extended_guest_request( + msg_type: SnpGuestRequestMsgType, + buffer: &mut [u8], + request_len: usize, + certs: &mut [u8], +) -> Result { + let mut cell = GREQ_DRIVER.lock(); + let driver: &mut SnpGuestRequestDriver = + cell.get_mut().ok_or_else(SvsmReqError::invalid_request)?; + driver.send_extended_guest_request(msg_type, buffer, request_len, certs) +} diff --git a/src/greq/mod.rs b/src/greq/mod.rs index 2184511be..8bbc905ef 100644 --- a/src/greq/mod.rs +++ b/src/greq/mod.rs @@ -4,4 +4,5 @@ // // Authors: Claudio Carvalho +pub mod driver; pub mod msg; diff --git a/src/sev/secrets_page.rs b/src/sev/secrets_page.rs index f8bf3a066..b07126fdc 100644 --- a/src/sev/secrets_page.rs +++ b/src/sev/secrets_page.rs @@ -9,6 +9,10 @@ use crate::sev::vmsa::VMPL_MAX; pub const VMPCK_SIZE: usize = 32; +extern "C" { + pub static mut SECRETS_PAGE: SecretsPage; +} + #[derive(Copy, Clone)] #[repr(C, packed)] pub struct SecretsPage { @@ -37,3 +41,16 @@ pub fn copy_secrets_page(target: &mut SecretsPage, source: VirtAddr) { *target = *table; } } + +pub fn is_vmpck0_clear() -> bool { + unsafe { SECRETS_PAGE.vmpck[0].iter().all(|e| *e == 0) } +} + +pub fn disable_vmpck0() { + unsafe { SECRETS_PAGE.vmpck[0].iter_mut().for_each(|e| *e = 0) }; + log::warn!("VMPCK0 disabled!"); +} + +pub fn get_vmpck0() -> [u8; VMPCK_SIZE] { + unsafe { SECRETS_PAGE.vmpck[0] } +} diff --git a/src/svsm.rs b/src/svsm.rs index 30122b427..90a13a41b 100644 --- a/src/svsm.rs +++ b/src/svsm.rs @@ -32,6 +32,7 @@ use svsm::elf; use svsm::error::SvsmError; use svsm::fs::{initialize_fs, populate_ram_fs}; use svsm::fw_cfg::FwCfg; +use svsm::greq::driver::guest_request_driver_init; use svsm::kernel_launch::KernelLaunchInfo; use svsm::mm::alloc::{memory_info, print_memory_info, root_mem_init}; use svsm::mm::memory::init_memory_map; @@ -471,6 +472,8 @@ pub extern "C" fn svsm_main() { panic!("Failed to validate flash memory: {:#?}", e); } + guest_request_driver_init(); + prepare_fw_launch(&fw_meta).expect("Failed to setup guest VMSA"); virt_log_usage(); diff --git a/src/utils/util.rs b/src/utils/util.rs index e38ae1116..48c1643a7 100644 --- a/src/utils/util.rs +++ b/src/utils/util.rs @@ -46,3 +46,11 @@ pub fn zero_mem_region(start: VirtAddr, end: VirtAddr) { // Zero region unsafe { start.as_mut_ptr::().write_bytes(0, size) } } + +/// Obtain bit for a given position +#[macro_export] +macro_rules! BIT { + ($x: expr) => { + (1 << ($x)) + }; +} From 228e9f6f5a6ebb8a28f76e478dbdf58c2ec49f10 Mon Sep 17 00:00:00 2001 From: Claudio Carvalho Date: Wed, 8 Nov 2023 09:02:44 +0200 Subject: [PATCH 8/9] svsm: Call disable_vmpck0() in the panic handler The panic handler is called when the SVSM state is not reliable any more. Disable the VMPCK0 key to prevent it from being exploited. Signed-off-by: Claudio Carvalho --- src/svsm.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/svsm.rs b/src/svsm.rs index 90a13a41b..9e5a1b980 100644 --- a/src/svsm.rs +++ b/src/svsm.rs @@ -42,7 +42,7 @@ use svsm::mm::{init_kernel_mapping_info, PerCPUPageMappingGuard, SIZE_1G}; use svsm::requests::{request_loop, update_mappings}; use svsm::serial::SerialPort; use svsm::serial::SERIAL_PORT; -use svsm::sev::secrets_page::{copy_secrets_page, SecretsPage}; +use svsm::sev::secrets_page::{copy_secrets_page, disable_vmpck0, SecretsPage}; use svsm::sev::sev_status_init; use svsm::sev::utils::{rmp_adjust, RMPFlags}; use svsm::svsm_console::SVSMIOPort; @@ -492,6 +492,8 @@ pub extern "C" fn svsm_main() { #[panic_handler] fn panic(info: &PanicInfo) -> ! { + disable_vmpck0(); + log::error!("Panic: CPU[{}] {}", this_cpu().get_apic_id(), info); print_stack(3); From fffd826534491acbb401baf00656fde6b2421ef3 Mon Sep 17 00:00:00 2001 From: Claudio Carvalho Date: Sun, 29 Oct 2023 15:46:35 +0200 Subject: [PATCH 9/9] greq: Add attestation report support Add get_regular_report() and get_extended_report(). They both call the SNP_GUEST_REQUEST driver to request a VMPL0 attestation report, the difference is that get_extended_report() also requests the SEV-SNP certificates needed to verify the attestation report. The get_extended_report() function will return an empty buffer if the SEV-SNP certificates where not imported yet, but they can be imported from the host using the github virtee/snphost project: $ snphost import For testing purposes, if you import PEM files that contain some random data, you should be able to see the same random data when you call get_extended_report() from the SVSM. Signed-off-by: Claudio Carvalho --- src/greq/mod.rs | 4 + src/greq/pld_report.rs | 192 +++++++++++++++++++++++++++++++++++++++++ src/greq/services.rs | 108 +++++++++++++++++++++++ 3 files changed, 304 insertions(+) create mode 100644 src/greq/pld_report.rs create mode 100644 src/greq/services.rs diff --git a/src/greq/mod.rs b/src/greq/mod.rs index 8bbc905ef..56aca7547 100644 --- a/src/greq/mod.rs +++ b/src/greq/mod.rs @@ -4,5 +4,9 @@ // // Authors: Claudio Carvalho +//! `SNP_GUEST_REQUEST` mechanism to communicate with the PSP + pub mod driver; pub mod msg; +pub mod pld_report; +pub mod services; diff --git a/src/greq/pld_report.rs b/src/greq/pld_report.rs new file mode 100644 index 000000000..fafa2ce09 --- /dev/null +++ b/src/greq/pld_report.rs @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (C) 2023 IBM +// +// Authors: Claudio Carvalho + +//! `SNP_GUEST_REQUEST` command to request an attestation report. + +extern crate alloc; + +use core::mem::size_of; + +use crate::protocols::errors::SvsmReqError; + +/// Size of the `SnpReportRequest.user_data` +pub const USER_DATA_SIZE: usize = 64; + +/// MSG_REPORT_REQ payload format (AMD SEV-SNP spec. table 20) +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct SnpReportRequest { + /// Guest-provided data to be included in the attestation report + /// REPORT_DATA (512 bits) + user_data: [u8; USER_DATA_SIZE], + /// The VMPL to put in the attestation report + vmpl: u32, + /// 31:2 - Reserved + /// 1:0 - KEY_SEL. Selects which key to use for derivation + /// 0: If VLEK is installed, sign with VLEK. Otherwise, sign with VCEK + /// 1: Sign with VCEK + /// 2: Sign with VLEK + /// 3: Reserved + flags: u32, + /// Reserved, must be zero + rsvd: [u8; 24], +} + +impl SnpReportRequest { + /// Take a slice and return a reference for Self + pub fn try_from_as_ref(buffer: &[u8]) -> Result<&Self, SvsmReqError> { + let buffer = buffer + .get(..size_of::()) + .ok_or_else(SvsmReqError::invalid_parameter)?; + + let request = unsafe { &*buffer.as_ptr().cast::() }; + + if !request.is_reserved_clear() { + return Err(SvsmReqError::invalid_parameter()); + } + Ok(request) + } + + pub fn is_vmpl0(&self) -> bool { + self.vmpl == 0 + } + + /// Check if the reserved field is clear + fn is_reserved_clear(&self) -> bool { + self.rsvd.into_iter().all(|e| e == 0) + } +} + +/// MSG_REPORT_RSP payload format (AMD SEV-SNP spec. table 23) +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct SnpReportResponse { + /// The status of the key derivation operation, see [SnpReportResponseStatus] + status: u32, + /// Size in bytes of the report + report_size: u32, + /// Reserved + _reserved: [u8; 24], + /// The attestation report generated by firmware + report: AttestationReport, +} + +/// Supported values for SnpReportResponse.status +#[repr(u32)] +#[derive(Clone, Copy, Debug)] +pub enum SnpReportResponseStatus { + Success = 0, + InvalidParameters = 0x16, + InvalidKeySelection = 0x27, +} + +impl SnpReportResponse { + pub fn try_from_as_ref(buffer: &[u8]) -> Result<&Self, SvsmReqError> { + let buffer = buffer + .get(..size_of::()) + .ok_or_else(SvsmReqError::invalid_parameter)?; + + let response = unsafe { &*buffer.as_ptr().cast::() }; + Ok(response) + } + + /// Validate the [SnpReportResponse] fields + /// + /// # Panic + /// + /// * The size of the struct [`AttestationReport`] must fit in a u32 + pub fn validate(&self) -> Result<(), SvsmReqError> { + if self.status != SnpReportResponseStatus::Success as u32 { + return Err(SvsmReqError::invalid_request()); + } + + const REPORT_SIZE: usize = size_of::(); + assert!(u32::try_from(REPORT_SIZE).is_ok()); + + if self.report_size != REPORT_SIZE as u32 { + return Err(SvsmReqError::invalid_format()); + } + + Ok(()) + } +} + +/// The `TCB_VERSION` contains the security version numbers of each +/// component in the trusted computing base (TCB) of the SNP firmware. +/// (AMD SEV-SNP spec. table 3) +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +struct TcbVersion { + /// Version of the Microcode, SNP firmware, PSP and boot loader + raw: u64, +} + +/// Format for an ECDSA P-384 with SHA-384 signature (AMD SEV-SNP spec. table 115) +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +struct Signature { + /// R component of this signature + r: [u8; 72], + /// S component of this signature + s: [u8; 72], + /// Reserved + reserved: [u8; 368], +} + +/// ATTESTATION_REPORT format (AMD SEV-SNP spec. table 21) +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct AttestationReport { + /// Version number of this attestation report + version: u32, + /// The guest SVN + guest_svn: u32, + /// The guest policy + policy: u64, + /// The family ID provided at launch + family_id: [u8; 16], + /// The image ID provided at launch + image_id: [u8; 16], + /// The request VMPL for the attestation report + vmpl: u32, + /// The signature algorithm used to sign this report + signature_algo: u32, + /// CurrentTcb + platform_version: TcbVersion, + /// Information about the platform + platform_info: u64, + /// Flags + flags: u32, + /// Reserved, must be zero + reserved0: u32, + /// Guest-provided data + report_data: [u8; 64], + /// The measurement calculated at launch + measurement: [u8; 48], + /// Data provided by the hypervisor at launch + host_data: [u8; 32], + /// SHA-384 digest of the ID public key that signed the ID block + /// provided in `SNP_LAUNCH_FINISH` + id_key_digest: [u8; 48], + /// SHA-384 digest of the Author public key that certified the ID key, + /// if provided in `SNP_LAUNCH_FINISH`. Zeroes if `AUTHOR_KEY_EN` is 1 + author_key_digest: [u8; 48], + /// Report ID of this guest + report_id: [u8; 32], + /// Report ID of this guest's migration agent + report_id_ma: [u8; 32], + /// Report TCB version used to derive the VCEK that signed this report + reported_tcb: TcbVersion, + /// Reserved + reserved1: [u8; 24], + /// If `MaskChipId` is set to 0, Identifier unique to the chip as + /// output by `GET_ID`. Otherwise, set to 0h + chip_id: [u8; 64], + /// Reserved and some more flags + reserved2: [u8; 192], + /// Signature of bytes 0h to 29Fh inclusive of this report + signature: Signature, +} diff --git a/src/greq/services.rs b/src/greq/services.rs new file mode 100644 index 000000000..1beebc2d4 --- /dev/null +++ b/src/greq/services.rs @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (C) 2023 IBM +// +// Authors: Claudio Carvalho + +//! API to send `SNP_GUEST_REQUEST` commands to the PSP + +extern crate alloc; + +use crate::{ + greq::{ + driver::{send_extended_guest_request, send_regular_guest_request}, + msg::SnpGuestRequestMsgType, + pld_report::{SnpReportRequest, SnpReportResponse}, + }, + protocols::errors::SvsmReqError, +}; +use core::mem::size_of; + +const REPORT_REQUEST_SIZE: usize = size_of::(); +const REPORT_RESPONSE_SIZE: usize = size_of::(); + +fn get_report(buffer: &mut [u8], certs: Option<&mut [u8]>) -> Result { + let request: &SnpReportRequest = SnpReportRequest::try_from_as_ref(buffer)?; + // Non-VMPL0 attestation reports can be requested by the guest kernel + // directly to the PSP. + if !request.is_vmpl0() { + return Err(SvsmReqError::invalid_parameter()); + } + let response_len = if certs.is_none() { + send_regular_guest_request( + SnpGuestRequestMsgType::ReportRequest, + buffer, + REPORT_REQUEST_SIZE, + )? + } else { + send_extended_guest_request( + SnpGuestRequestMsgType::ReportRequest, + buffer, + REPORT_REQUEST_SIZE, + certs.unwrap(), + )? + }; + if REPORT_RESPONSE_SIZE > response_len { + return Err(SvsmReqError::invalid_request()); + } + let response: &SnpReportResponse = SnpReportResponse::try_from_as_ref(buffer)?; + response.validate()?; + + Ok(response_len) +} + +/// Request a regular VMPL0 attestation report to the PSP. +/// +/// Use the `SNP_GUEST_REQUEST` driver to send the provided `MSG_REPORT_REQ` command to +/// the PSP. The VPML field of the command must be set to zero. +/// +/// The VMPCK0 is disabled for subsequent calls if this function fails in a way that +/// the VM state can be compromised. +/// +/// # Arguments +/// +/// * `buffer`: Buffer with the [`MSG_REPORT_REQ`](SnpReportRequest) command that will be +/// sent to the PSP. It must be large enough to hold the +/// [`MSG_REPORT_RESP`](SnpReportResponse) received from the PSP. +/// +/// # Returns +/// +/// * Success +/// * `usize`: Number of bytes written to `buffer`. It should match the +/// [`MSG_REPORT_RESP`](SnpReportResponse) size. +/// * Error +/// * [`SvsmReqError`] +pub fn get_regular_report(buffer: &mut [u8]) -> Result { + get_report(buffer, None) +} + +/// Request an extended VMPL0 attestation report to the PSP. +/// +/// We say that it is extended because it requests a VMPL0 attestation report +/// to the PSP (as in [`get_regular_report()`]) and also requests to the hypervisor +/// the certificates required to verify the attestation report. +/// +/// The VMPCK0 is disabled for subsequent calls if this function fails in a way that +/// the VM state can be compromised. +/// +/// # Arguments +/// +/// * `buffer`: Buffer with the [`MSG_REPORT_REQ`](SnpReportRequest) command that will be +/// sent to the PSP. It must be large enough to hold the +/// [`MSG_REPORT_RESP`](SnpReportResponse) received from the PSP. +/// * `certs`: Buffer to store the SEV-SNP certificates received from the hypervisor. +/// +/// # Return codes +/// +/// * Success +/// * `usize`: Number of bytes written to `buffer`. It should match +/// the [`MSG_REPORT_RESP`](SnpReportResponse) size. +/// * Error +/// * [`SvsmReqError`] +/// * `SvsmReqError::FatalError(SvsmError::Ghcb(GhcbError::VmgexitError(certs_buffer_size, psp_rc)))`: +/// * `certs` is not large enough to hold the certificates. +/// * `certs_buffer_size`: number of bytes required. +/// * `psp_rc`: PSP return code +pub fn get_extended_report(buffer: &mut [u8], certs: &mut [u8]) -> Result { + get_report(buffer, Some(certs)) +}