diff --git a/Cargo.lock b/Cargo.lock index 2bc4038f72..7b7cba9fc0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,16 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aead" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99" +dependencies = [ + "crypto-common 0.2.2", + "inout 0.2.5", +] + [[package]] name = "aes" version = "0.8.4" @@ -15,11 +25,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", - "cipher", - "cpufeatures", + "cipher 0.4.4", + "cpufeatures 0.2.17", "zeroize", ] +[[package]] +name = "aes" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35f0f96ce78e38c3dc6d8948aa8163d06385be74000f3c7a95bf1eef35d3ea32" +dependencies = [ + "cipher 0.5.2", + "cpubits", + "cpufeatures 0.3.1", +] + +[[package]] +name = "aes-gcm" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2b8006a0c83f52b62ba44a97b58bf76fe2f70a329e588f67f89691d93d498f" +dependencies = [ + "aead", + "aes 0.9.3", + "cipher 0.5.2", + "ctr 0.10.1", + "ctutils", + "ghash", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -222,6 +257,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + [[package]] name = "bstr" version = "1.12.1" @@ -304,11 +348,22 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", - "inout", + "crypto-common 0.1.6", + "inout 0.1.4", "zeroize", ] +[[package]] +name = "cipher" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", + "inout 0.2.5", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -360,6 +415,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + [[package]] name = "cms" version = "0.2.3" @@ -432,6 +493,12 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -441,6 +508,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -497,13 +573,40 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + [[package]] name = "ctr" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ - "cipher", + "cipher 0.4.4", +] + +[[package]] +name = "ctr" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baaca1c4b237092596f64d571e9db6ce4109c4ef9742e27590f1709594461f21" +dependencies = [ + "cipher 0.5.2", +] + +[[package]] +name = "ctutils" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03bb0e1cc970d482d121d9a1744999169b69a07470b3d644a7894e53fcaf4574" +dependencies = [ + "cmov", ] [[package]] @@ -665,9 +768,9 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", "const-oid", - "crypto-common", + "crypto-common 0.1.6", "subtle", ] @@ -1006,6 +1109,15 @@ dependencies = [ "syn", ] +[[package]] +name = "ghash" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eecf2d5dc9b66b732b97707a0210906b1d30523eb773193ab777c0c84b3e8d5" +dependencies = [ + "polyval", +] + [[package]] name = "glob" version = "0.3.3" @@ -1129,6 +1241,15 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "hybrid-array" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "1.8.1" @@ -1355,6 +1476,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "inout" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b5cf4372d022092674a83b8febf5865f7a573cc0c5baec5e49c879b6a20978f" +dependencies = [ + "hybrid-array", +] + [[package]] name = "insta" version = "1.43.2" @@ -1633,6 +1763,7 @@ dependencies = [ name = "litebox_platform_lvbs" version = "0.1.0" dependencies = [ + "aes-gcm", "aligned-vec", "arrayvec", "bitflags 2.13.1", @@ -1643,6 +1774,7 @@ dependencies = [ "litebox_common_linux", "litebox_common_lvbs", "litebox_util_log", + "log", "modular-bitfield", "num_enum", "once_cell", @@ -1650,6 +1782,7 @@ dependencies = [ "rand_core", "rangemap", "raw-cpuid", + "rsa", "sha2", "spin 0.10.0", "thiserror", @@ -1808,8 +1941,8 @@ dependencies = [ name = "litebox_shim_optee" version = "0.1.0" dependencies = [ - "aes", - "ctr", + "aes 0.8.4", + "ctr 0.9.2", "ecdsa", "elf", "hashbrown", @@ -2284,6 +2417,17 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +[[package]] +name = "polyval" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0fa31d631f2b2cb2a544d0aa321ce847a94764d701ca2becc411138b93d49cd" +dependencies = [ + "cpubits", + "cpufeatures 0.3.1", + "universal-hash", +] + [[package]] name = "portable-atomic" version = "1.11.1" @@ -2762,7 +2906,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -2773,7 +2917,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -3337,6 +3481,16 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "universal-hash" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96" +dependencies = [ + "crypto-common 0.2.2", + "ctutils", +] + [[package]] name = "url" version = "2.5.8" diff --git a/dev_tests/src/ratchet.rs b/dev_tests/src/ratchet.rs index fcda34e79b..2437d867bd 100644 --- a/dev_tests/src/ratchet.rs +++ b/dev_tests/src/ratchet.rs @@ -37,9 +37,9 @@ fn ratchet_globals() -> Result<()> { ("litebox/", 10), ("litebox_platform_linux_kernel/", 5), ("litebox_platform_linux_userland/", 6), - ("litebox_platform_lvbs/", 25), + ("litebox_platform_lvbs/", 26), ("litebox_platform_windows_userland/", 10), - ("litebox_runner_lvbs/", 8), + ("litebox_runner_lvbs/", 7), ("litebox_runner_snp/", 2), ("litebox_shim_linux/", 1), ("litebox_shim_optee/", 7), diff --git a/litebox_common_lvbs/src/lib.rs b/litebox_common_lvbs/src/lib.rs index 714999e0d0..52dcb2b433 100644 --- a/litebox_common_lvbs/src/lib.rs +++ b/litebox_common_lvbs/src/lib.rs @@ -52,6 +52,7 @@ pub const VSM_VTL_CALL_FUNC_ID_SET_PLATFORM_ROOT_KEY: u32 = 0x1_ffed; // This VSM function ID for generating the identity signing key is subject to change pub const VSM_VTL_CALL_FUNC_ID_GENERATE_IDENTITY_SIGNING_KEY: u32 = 0x1_ffee; +pub const VSM_VTL_CALL_FUNC_ID_SET_CRASH_DUMP_KEY: u32 = 0x1_ffef; // This VSM function ID for OP-TEE messages is subject to change pub const VSM_VTL_CALL_FUNC_ID_OPTEE_MESSAGE: u32 = 0x1_fff0; @@ -77,6 +78,7 @@ pub enum VsmFunction { AllocateRingbufferMemory = VSM_VTL_CALL_FUNC_ID_ALLOCATE_RINGBUFFER_MEMORY, SetPlatformRootKey = VSM_VTL_CALL_FUNC_ID_SET_PLATFORM_ROOT_KEY, GenerateIdentitySigningKey = VSM_VTL_CALL_FUNC_ID_GENERATE_IDENTITY_SIGNING_KEY, + SetCrashDumpKey = VSM_VTL_CALL_FUNC_ID_SET_CRASH_DUMP_KEY, } // `HV_STATUS_*` constants used as discriminants for `HypervCallError`. @@ -289,6 +291,9 @@ pub enum VsmError { #[error("symbol name contains invalid UTF-8")] SymbolNameInvalidUtf8, + + #[error("invalid key")] + InvalidKey, } impl From for VsmError { @@ -898,6 +903,11 @@ pub trait Vtl1Gate { /// Read the platform root key from VTL0 `key_pa` and store it in VTL1 state. fn set_platform_root_key(&self, key_pa: u64) -> Result<(), VsmError>; + /// Read the crash dump public key from VTL0 'key_pa'. + /// TODO: The key may be provisioned by a VTL0 agent eg. Azure Watson + /// after end-of boot, so certificate chain will need verifying + fn set_crash_dump_key(&self, key_pa: u64, key_len: u64) -> Result<(), VsmError>; + /// Close VTL1's window of trusting VTL0, making /// [`Vtl0Gate::end_of_boot_reached`] report `true` from here on. One-way: /// the window never reopens. diff --git a/litebox_platform_lvbs/Cargo.toml b/litebox_platform_lvbs/Cargo.toml index a66c107cd5..7e04031e37 100644 --- a/litebox_platform_lvbs/Cargo.toml +++ b/litebox_platform_lvbs/Cargo.toml @@ -33,6 +33,9 @@ rand_chacha = { version = "0.3.1", default-features = false } rand_core = { version = "0.6.4", default-features = false } zerocopy = { version = "0.8", default-features = false, features = ["derive"] } zeroize = { version = "1.8", default-features = false, features = ["alloc"] } +log = { version = "0.4.28", default-features = false } +rsa = { version = "0.9.10", default-features = false } +aes-gcm = { version = "0.11.1", default-features = false, features = ["aes", "alloc"] } [target.'cfg(target_arch = "x86_64")'.dependencies] x86_64 = { version = "0.15.2", default-features = false, features = ["instructions"] } diff --git a/litebox_platform_lvbs/src/arch/x86/ioport.rs b/litebox_platform_lvbs/src/arch/x86/ioport.rs index 0136e4f388..0ce97f9735 100644 --- a/litebox_platform_lvbs/src/arch/x86/ioport.rs +++ b/litebox_platform_lvbs/src/arch/x86/ioport.rs @@ -3,7 +3,6 @@ //! I/O Port-based serial communication -use crate::mshv::ringbuffer::ringbuffer; use core::{arch::asm, fmt}; use spin::{Mutex, Once}; @@ -138,8 +137,9 @@ impl ComPort { } } +static COM_ONCE: Once> = Once::new(); + fn com() -> &'static Mutex { - static COM_ONCE: Once> = Once::new(); COM_ONCE.call_once(|| { let mut com_port = ComPort::new(DEST_COM_PORT); com_port.init(); @@ -158,9 +158,6 @@ impl fmt::Write for ComPort { pub fn print(args: ::core::fmt::Arguments) { use core::fmt::Write; let _ = com().lock().write_fmt(args); - if let Some(rb) = ringbuffer() { - let _ = rb.lock().write_fmt(args); - } } #[macro_export] @@ -188,3 +185,13 @@ macro_rules! debug_serial_println { pub fn serial_print_string(s: &str) { com().lock().write_string(s); } + +// On panic, use COM_ONCE instead of com() to avoid possible +// first time init, and also avoid waiting for lock while +// another CPU may be writing to a "slow" serial port. +// Potentially garbled log on panic is better than no log +pub fn serial_print_panic_string(s: &str) { + if let Some(mut com) = COM_ONCE.get().and_then(Mutex::try_lock) { + com.write_string(s); + } +} diff --git a/litebox_platform_lvbs/src/crash_log.rs b/litebox_platform_lvbs/src/crash_log.rs new file mode 100644 index 0000000000..08b9ec2534 --- /dev/null +++ b/litebox_platform_lvbs/src/crash_log.rs @@ -0,0 +1,281 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +//! Crash log facility for LVBS +//! Log to internal VTL1 only ringbuffer, optionally mirroring +//! to serial port if debug is enabled. On panic, crash logger +//! will encrypt VTL1 buffer and dump it into VTL0 ringbuffer. +//! See `CrashDumpHeader` for crash dump format. + +use aes_gcm::{AeadInOut, Aes256Gcm, KeyInit, Nonce}; +use core::{ + panic::PanicInfo, + sync::atomic::{AtomicBool, Ordering}, +}; +use litebox_common_lvbs::VsmError; +use log; +use rand_core::CryptoRngCore; +use rsa::{ + Oaep, RsaPublicKey, + pkcs8::{DecodePublicKey, EncodePublicKey}, +}; +use sha2::{Digest, Sha256}; +use spin::{Mutex, Once}; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; +use zeroize::Zeroizing; + +use crate::arch::ioport::{serial_print_panic_string, serial_print_string}; + +// Allocated in .bss which is in .data (x86_64_vtl1.ld), so keep +// reasoable size. Must be small enough to fit in VTL0 buffer (64K) +// along with crash dump metadata. +// TODO: Check VTL0 ringbuffer is always big enough +const CRASH_LOG_BUFFER_SIZE: usize = 16 * 1024; +// Large enough for RSA-2048 PEM publik key and SPKI header +const CRASH_LOG_MAX_PUBLIC_KEY_SIZE: usize = 512; + +/// Unencrypted header describing crash dump. It will act as the AEAD +/// for the AES-GCM ecryptioon of the VTL1 ringbuffer. +/// Crash dump format: +/// ```text +/// +-----------------+-------------+------------+------+ +/// | CrashDumpHeader | Wrapped Key | Ciphertext | Tag | +/// +-----------------+-------------+------------+------+ +/// ``` +#[derive(Clone, Copy, Debug, FromBytes, IntoBytes, Immutable, KnownLayout)] +#[repr(C)] +struct CrashDumpHeader { + magic: [u8; 8], + version: u32, + header_size: u32, + wrapped_key_size: u32, + ciphertext_size: u32, + crash_dump_key_id: [u8; CRASH_DUMP_KEY_ID_SIZE], + nonce: [u8; CRASH_DUMP_NONCE_SIZE], +} + +/// Allocation free ring buffer allows early log capture before heap setup. +struct ConstRingBuffer { + buf: [u8; N], + head: usize, + len: usize, +} + +impl ConstRingBuffer { + const fn new() -> Self { + Self { + buf: [0u8; N], + head: 0, + len: 0, + } + } + + fn write(&mut self, data: &[u8]) { + if N == 0 || data.is_empty() { + return; + } + let data = if data.len() > N { + &data[data.len() - N..] + } else { + data + }; + let tail = (self.head + self.len) % N; + let first_len = data.len().min(N - tail); + self.buf[tail..tail + first_len].copy_from_slice(&data[..first_len]); + self.buf[..data.len() - first_len].copy_from_slice(&data[first_len..]); + + self.head = (self.head + data.len()) % N; + self.len = (self.len + data.len()).min(N); + } + + // Rotate buffer such that head is at position 0. Although O(n), + // but only called once, on crash dump, removing the need to + // allocate another buffer to copy into. + fn read_contiguous(&mut self) -> &mut [u8] { + self.buf[..N].rotate_left(self.head); + self.head = 0; + &mut self.buf[..self.len] + } + + fn len(&self) -> usize { + self.len + } +} + +const CRASH_DUMP_MAGIC: [u8; 8] = *b"LVBSDUMP"; +const CRASH_DUMP_VERSION: u32 = 0x00_00_00_01; +// RSA-2048 DER is ~294 bytes, PEM is ~450 bytes +const CRASH_DUMP_KEY_MAX_SIZE: usize = 512; +// SHA-256 hash of crash dump key +const CRASH_DUMP_KEY_ID_SIZE: usize = 32; +// Up to AES-256-GCM +const CRASH_DUMP_DATA_KEY_SIZE: usize = 32; +// RSA-2048 encryption +const CRASH_DUMP_WRAPPED_DATA_KEY_SIZE: usize = 256; +const CRASH_DUMP_NONCE_SIZE: usize = 12; +const CRASH_DUMP_TAG_SIZE: usize = 16; + +/// Construct parts of the sealed envelope before a panic to avoid +/// locks snd seeding delays in rng when generating nonce and DEK +struct PreparedEnvelope { + // Prepared AES-256-GCM cipher consisting of: + // - KEK: Public key used to encrypt symmetric key + // crash_dump_key: [u8; CRASH_DUMP_KEY_SIZE], + // - DEK: Generated symmetric key to encrypt crash data + // data_key: Zeroizing<[u8; CRASH_DUMP_DATA_KEY_SIZE]>, + cipher: Aes256Gcm, + // Shorter hash of public KEK DER copied into crash dump header + crash_dump_key_id: [u8; CRASH_DUMP_KEY_ID_SIZE], + // DEK encrypted with KEK + wrapped_data_key: [u8; CRASH_DUMP_WRAPPED_DATA_KEY_SIZE], + nonce: [u8; CRASH_DUMP_NONCE_SIZE], +} + +impl PreparedEnvelope { + pub fn new(key_der: &[u8], rng: &mut impl CryptoRngCore) -> Result { + if key_der.is_empty() || key_der.len() > CRASH_DUMP_KEY_MAX_SIZE { + return Err(VsmError::InvalidKey); + } + // Only SPKI supported. TODO: check if PKCS1 is needed. + let crash_dump_key = + RsaPublicKey::from_public_key_der(key_der).map_err(|_| VsmError::InvalidKey)?; + let crash_dump_key_id: [u8; CRASH_DUMP_KEY_ID_SIZE] = Sha256::digest( + crash_dump_key + .to_public_key_der() + .map_err(|_| VsmError::InvalidKey)? + .as_bytes(), + ) + .into(); + + let mut data_key = Zeroizing::new([0u8; CRASH_DUMP_DATA_KEY_SIZE]); + rng.fill_bytes(&mut *data_key); + let wrapped_data_key = crash_dump_key + .encrypt(rng, Oaep::new::(), &*data_key) + .map_err(|_| VsmError::InvalidKey)? + .as_slice() + .try_into() + .map_err(|_| VsmError::InvalidKey)?; + + let cipher = Aes256Gcm::new_from_slice(&*data_key).map_err(|_| VsmError::InvalidKey)?; + + Ok(Self { + cipher, + crash_dump_key_id, + wrapped_data_key, + nonce: [0u8; CRASH_DUMP_NONCE_SIZE], + }) + } + + fn seal(&self, aead: &[u8], data: &mut [u8]) -> Result<[u8; CRASH_DUMP_TAG_SIZE], VsmError> { + let tag = self + .cipher + .encrypt_inout_detached(&Nonce::from(self.nonce), aead, data.into()) + .map_err(|_| VsmError::InvalidKey)?; + Ok(tag.into()) + } +} + +/// Logger implements `log` facade and crash dump logic +pub struct CrashLogger { + ring_buf: Mutex>, + envelope: Mutex>, + serial_mirror: AtomicBool, + panic_dump_started: AtomicBool, +} + +impl CrashLogger { + const fn new() -> Self { + Self { + ring_buf: Mutex::new(ConstRingBuffer::new()), + envelope: Mutex::new(None), + serial_mirror: AtomicBool::new(false), + panic_dump_started: AtomicBool::new(false), + } + } + + pub fn mirror_to_serial(&self, enable: bool) { + self.serial_mirror.store(enable, Ordering::Relaxed); + } + + pub fn set_public_key( + &self, + key_der: &[u8], + rng: &mut impl CryptoRngCore, + ) -> Result<(), VsmError> { + let p = PreparedEnvelope::new(key_der, rng)?; + if key_der.is_empty() || key_der.len() > CRASH_LOG_MAX_PUBLIC_KEY_SIZE { + return Err(VsmError::InvalidKey); + } + //TODO; Check if we are already panicking + self.envelope.lock().replace(p); + Ok(()) + } + + pub fn dump_on_panic(&self, _info: &PanicInfo) -> Result<(), VsmError> { + if self.panic_dump_started.swap(true, Ordering::SeqCst) { + serial_print_panic_string("Crash dump already progress"); + return Err(VsmError::OutOfMemory); + } + + let Some(envelope) = &*self.envelope.lock() else { + return Err(VsmError::InvalidKey); + }; + + let ring_buffer = &mut *self.ring_buf.lock(); + + let aead = CrashDumpHeader { + magic: CRASH_DUMP_MAGIC, + version: CRASH_DUMP_VERSION, + header_size: u32::try_from(core::mem::size_of::()) + .map_err(|_| VsmError::IntegerOverflow)?, + wrapped_key_size: u32::try_from(CRASH_DUMP_WRAPPED_DATA_KEY_SIZE) + .map_err(|_| VsmError::IntegerOverflow)?, + ciphertext_size: u32::try_from(ring_buffer.len()) + .map_err(|_| VsmError::IntegerOverflow)?, + crash_dump_key_id: envelope.crash_dump_key_id, + nonce: envelope.nonce, + }; + + let tag = envelope.seal(aead.as_bytes(), ring_buffer.read_contiguous())?; + // TODO: ringbuffer must already be created, otherwwisr pvmap might hold some locks? + let Some(vtl0_buf) = crate::mshv::ringbuffer::ringbuffer() else { + // TODO: Make errors from module more descriptive + return Err(VsmError::InvalidKey); + }; + let mut vtl0_buf = vtl0_buf.lock(); + vtl0_buf.write(aead.as_bytes()); + vtl0_buf.write(&envelope.wrapped_data_key); + vtl0_buf.write(&envelope.crash_dump_key_id); + vtl0_buf.write(ring_buffer.read_contiguous()); + vtl0_buf.write(&tag); + + Ok(()) + } +} + +impl log::Log for CrashLogger { + fn enabled(&self, _metadata: &log::Metadata) -> bool { + true + } + + fn log(&self, record: &log::Record) { + let mut buf: arrayvec::ArrayString<1024> = arrayvec::ArrayString::new(); + if litebox_util_log::format_record(&mut buf, record).is_ok() { + self.ring_buf.lock().write(buf.as_bytes()); + } + if self.serial_mirror.load(Ordering::Relaxed) { + serial_print_string(&buf); + } + //TODO: thers should be some policy on the minimum key modulus + } + + fn flush(&self) {} +} + +pub fn crash_logger() -> &'static CrashLogger { + static CRASH_LOGGER_ONCE: Once = Once::new(); + CRASH_LOGGER_ONCE.call_once(|| { + serial_print_string("***Setting up logger..."); + CrashLogger::new() + }) +} diff --git a/litebox_platform_lvbs/src/host/lvbs_impl.rs b/litebox_platform_lvbs/src/host/lvbs_impl.rs index f84a18a758..262e54bd01 100644 --- a/litebox_platform_lvbs/src/host/lvbs_impl.rs +++ b/litebox_platform_lvbs/src/host/lvbs_impl.rs @@ -296,7 +296,7 @@ fn crng_seed_in_vault(rdrand: CrngSeed) -> Option { Some(ctx.1) } -fn rdrand_seed() -> Option { +pub(crate) fn rdrand_seed() -> Option { let mut seed = CrngSeed::default(); for chunk in seed.chunks_mut(8) { let mut word = 0; diff --git a/litebox_platform_lvbs/src/lib.rs b/litebox_platform_lvbs/src/lib.rs index 62896d3999..6eb929b8f3 100644 --- a/litebox_platform_lvbs/src/lib.rs +++ b/litebox_platform_lvbs/src/lib.rs @@ -39,6 +39,7 @@ use crate::mm::vmap::vmap_allocator; extern crate alloc; pub mod arch; +pub mod crash_log; pub mod host; pub mod mm; pub mod mshv; diff --git a/litebox_platform_lvbs/src/mshv/ringbuffer.rs b/litebox_platform_lvbs/src/mshv/ringbuffer.rs index b98262c806..c90e5b794b 100644 --- a/litebox_platform_lvbs/src/mshv/ringbuffer.rs +++ b/litebox_platform_lvbs/src/mshv/ringbuffer.rs @@ -29,7 +29,7 @@ impl RingBuffer { phys_addr: PhysAddr, requested_size: usize, ) -> Self { - let pa: usize = phys_addr.as_u64().trunc(); + let pa: usize = phys_addr.as_u64().trunc(); //TODO: We should make these requirements let fast_path_eligible = requested_size > 0 && requested_size.is_multiple_of(PAGE_SIZE) && pa.is_multiple_of(PAGE_SIZE); diff --git a/litebox_platform_lvbs/src/mshv/vsm.rs b/litebox_platform_lvbs/src/mshv/vsm.rs index cb481e0f1d..960ab41700 100644 --- a/litebox_platform_lvbs/src/mshv/vsm.rs +++ b/litebox_platform_lvbs/src/mshv/vsm.rs @@ -25,11 +25,13 @@ use crate::{ use alloc::vec::Vec; use core::ops::Range; use litebox::mm::vmap::{PhysPageAddr, PhysPageAddrArray, PhysPointerError}; +use litebox::platform::CrngProvider; use litebox::utils::TruncateExt; use litebox_common_lvbs::{ FrameTxn, HypervCallError, MemAttr, PAGE_SHIFT, PAGE_SIZE, ReservationStatus, VsmError, Vtl0Gate, Vtl0PrivilegedWrite, Vtl1Gate, }; +use rand_core::SeedableRng; use rangemap::RangeSet; use spin::{Once, rwlock::RwLock as SpinRwLock}; use x86_64::{ @@ -800,4 +802,22 @@ impl Vtl1Gate for LvbsVtl1Gate { Err(crate::vault::InstallError::Fill(e)) => Err(e), } } + + fn set_crash_dump_key(&self, key_pa: u64, key_len: u64) -> Result<(), VsmError> { + let key_pa = PhysAddr::try_new(key_pa).map_err(|_| VsmError::InvalidPhysicalAddress)?; + let key_len = usize::try_from(key_len) + .ok() + .filter(|len| (1..=PAGE_SIZE).contains(len)) + .ok_or(VsmError::InvalidKey)?; + let mut key_der = alloc::vec![0u8; key_len]; + LvbsVtl0Gate::mint(self.platform) + .read_vtl0_contiguous(key_pa.as_u64(), &mut key_der) + .map_err(|_| VsmError::Vtl0CopyFailed)?; + let mut seed = [0u8; 32]; + self.platform.fill_bytes_crng(&mut seed); + let seed = crate::host::lvbs_impl::rdrand_seed().ok_or(VsmError::InvalidKey)?; + let mut rng = rand_chacha::ChaCha20Rng::from_seed(seed); + crate::crash_log::crash_logger().set_public_key(&key_der, &mut rng)?; + Ok(()) + } } diff --git a/litebox_runner_lvbs/src/lib.rs b/litebox_runner_lvbs/src/lib.rs index e74575acec..70adae0cc2 100644 --- a/litebox_runner_lvbs/src/lib.rs +++ b/litebox_runner_lvbs/src/lib.rs @@ -358,6 +358,7 @@ fn vsm_dispatch(platform: &'static Platform, func_id: VsmFunction, params: &[u64 Err(VsmError::OperationNotSupported("Identity key generation")) } VsmFunction::OpteeMessage => Err(VsmError::OperationNotSupported("OP-TEE communication")), + VsmFunction::SetCrashDumpKey => Err(VsmError::OperationNotSupported("Set Crash Dump Key")), }; match result { Ok(value) => value, @@ -1858,5 +1859,6 @@ fn register_embedded_tas(shim: &litebox_shim_optee::OpteeShim) { fn panic(info: &PanicInfo) -> ! { litebox_platform_lvbs::on_panic(info.location()); serial_println!("{}", info); + let _ = litebox_platform_lvbs::crash_log::crash_logger().dump_on_panic(info); panic_to_vtl0() } diff --git a/litebox_runner_lvbs/src/main.rs b/litebox_runner_lvbs/src/main.rs index 7f86f1f7c7..6a59eac008 100644 --- a/litebox_runner_lvbs/src/main.rs +++ b/litebox_runner_lvbs/src/main.rs @@ -9,6 +9,7 @@ use core::arch::{asm, naked_asm}; use core::sync::atomic::{AtomicBool, Ordering}; use litebox_platform_lvbs::{ arch::{enable_extended_states, enable_fsgsbase, enable_smep_smap, enable_umip}, + crash_log, host::{ bootparam::save_boot_info, per_cpu_variables::{ @@ -21,25 +22,6 @@ use litebox_platform_lvbs::{ use x86_64::VirtAddr; use x86_64::structures::paging::PageTableFlags; -/// `log` backend that forwards to the serial console. -struct HostLogger; - -impl log::Log for HostLogger { - fn enabled(&self, _metadata: &log::Metadata) -> bool { - true - } - - fn log(&self, record: &log::Record) { - let mut buf: arrayvec::ArrayString<1024> = arrayvec::ArrayString::new(); - let _ = litebox_util_log::format_record(&mut buf, record); - litebox_platform_lvbs::arch::ioport::serial_print_string(&buf); - } - - fn flush(&self) {} -} - -static HOST_LOGGER: HostLogger = HostLogger; - /// Spinlock protecting the shared AP boot stack (`VTL1_KERNEL_STACK_PAGE`). /// /// All APs receive the same initial RSP via `hvcall_enable_vp_vtl`. VTL0 @@ -447,11 +429,14 @@ pub unsafe extern "C" fn _start(possible_cpus: u64, mem_pa: u64, mem_size: u64) unsafe extern "C" fn kernel_main(is_bsp: bool) -> ! { if is_bsp { - let _ = log::set_logger(&HOST_LOGGER); - #[cfg(debug_assertions)] - log::set_max_level(log::LevelFilter::Debug); - #[cfg(not(debug_assertions))] - log::set_max_level(log::LevelFilter::Warn); + let _ = log::set_logger(crash_log::crash_logger()); + + if cfg!(debug_assertions) { + crash_log::crash_logger().mirror_to_serial(true); + log::set_max_level(log::LevelFilter::Debug); + } else { + log::set_max_level(log::LevelFilter::Warn); + } serial_println!("=============================="); serial_println!(" Hello from LiteBox for LVBS! ");