From b8df8aa4ff3b6aa3a8546f7608129650fdb7a7ca Mon Sep 17 00:00:00 2001 From: Femi Adeyemi Date: Wed, 21 Oct 2026 18:27:59 +0000 Subject: [PATCH] WIP: crash_log: Dump encrypted log on panic 1. Replace HOST_LOGGER with CRASH_LOGGER. It logs to VTL1 allocated memory instead of serial port and VTL0 ringbuffer. On debug builds, it optionally mirrors to the serial port. 2. VTL0 will a use a new SetCrashDumpKey API to send LiteBox a public key that will be used for crash dump envelope encryption. 3. On panic, the VTL1 logs are encrypted with a symmetric AES-256-GCM key, and that key itself is encrypted by the public key sent by VTL0. The crash dump envelope is then written to VTL0 ringbuffer in the following format (see CrashDumpHeader for details): CrashDumpHeader(AEAD) | Encrypted Key | Log Ciphertext | Tag 4. Design Choices: - VTL1 logs are stored in a const-size ringbuffer available at boot. This implies some space in .bss/.data section. - Envelope cipher is prepared on public key provision to avoid RNG availability in panic path. 5. TODOS: - On vault panic, what can be dumped, if any. - Test exceptions (div/0,... ), VTL0 panic paths. - VTL0 ringbuffer writes can be simplified if we insist on a page aligned buffer. --- Cargo.lock | 176 +++++++++++- dev_tests/src/ratchet.rs | 4 +- litebox_common_lvbs/src/lib.rs | 10 + litebox_platform_lvbs/Cargo.toml | 3 + litebox_platform_lvbs/src/arch/x86/ioport.rs | 17 +- litebox_platform_lvbs/src/crash_log.rs | 281 +++++++++++++++++++ litebox_platform_lvbs/src/host/lvbs_impl.rs | 2 +- litebox_platform_lvbs/src/lib.rs | 1 + litebox_platform_lvbs/src/mshv/ringbuffer.rs | 2 +- litebox_platform_lvbs/src/mshv/vsm.rs | 20 ++ litebox_runner_lvbs/src/lib.rs | 2 + litebox_runner_lvbs/src/main.rs | 33 +-- 12 files changed, 507 insertions(+), 44 deletions(-) create mode 100644 litebox_platform_lvbs/src/crash_log.rs 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! ");