From 5d4880656832777f4f9169b2db2d39bbe130e22b Mon Sep 17 00:00:00 2001 From: Christopher Zurcher Date: Mon, 2 Dec 2024 17:01:41 -0800 Subject: [PATCH] [CONFLICT] MsWheaPkg: Add Rust Telemetry helper library (#611) ## Description This adds a rust version of the existing MuTelemetryHelperLib - [ ] Impacts functionality? - [ ] Impacts security? - [ ] Breaking change? - [x] Includes tests? - [x] Includes documentation? - [ ] Backport to release branch? ## How This Was Tested Rust unit tests covering the correct propagation of data through the log_telemetry and report_status_code interfaces, as well as data struct size checks and a coverage check. ## Integration Instructions Parent driver must call `init_telemetry` once, and can then call `log_telemetry` as needed. --- Cargo.toml | 10 +- .../Crates/MuTelemetryHelperLib/Cargo.toml | 21 ++ .../Crates/MuTelemetryHelperLib/src/lib.rs | 268 ++++++++++++++++++ .../src/status_code_runtime.rs | 85 ++++++ 4 files changed, 383 insertions(+), 1 deletion(-) create mode 100644 MsWheaPkg/Crates/MuTelemetryHelperLib/Cargo.toml create mode 100644 MsWheaPkg/Crates/MuTelemetryHelperLib/src/lib.rs create mode 100644 MsWheaPkg/Crates/MuTelemetryHelperLib/src/status_code_runtime.rs diff --git a/Cargo.toml b/Cargo.toml index ea5bc470eb..948a467e91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,8 @@ members = [ "HidPkg/Crates/HiiKeyboardLayout", "HidPkg/UefiHidDxe", "HidPkg/UefiHidDxeV2", - "MsCorePkg/HelloWorldRustDxe" + "MsCorePkg/HelloWorldRustDxe", + "MsWheaPkg/Crates/MuTelemetryHelperLib" ] # Add packages that generate libraries here @@ -17,6 +18,13 @@ RustBootServicesAllocatorDxe = {path = "MsCorePkg/Crates/RustBootServicesAllocat HidIo = {path = "HidPkg/Crates/HidIo"} hidparser = {git = "https://github.com/microsoft/mu_rust_hid.git", branch = "main"} HiiKeyboardLayout = {path = "HidPkg/Crates/HiiKeyboardLayout"} +<<<<<<< HEAD +======= +mu_pi = {git = "https://github.com/microsoft/mu_rust_pi.git", tag = "v5.1.0"} +mu_rust_helpers = { git = "https://github.com/microsoft/mu_rust_helpers.git", tag = "v1.2.1" } +boot_services = { git = "https://github.com/microsoft/mu_rust_helpers.git", tag = "v1.2.1" } +MuTelemetryHelperLib = {path = "MsWheaPkg/Crates/MuTelemetryHelperLib"} +>>>>>>> 239f3ee1 (MsWheaPkg: Add Rust Telemetry helper library (#611)) memoffset = "0.9.0" num-traits = { version = "0.2", default-features = false} diff --git a/MsWheaPkg/Crates/MuTelemetryHelperLib/Cargo.toml b/MsWheaPkg/Crates/MuTelemetryHelperLib/Cargo.toml new file mode 100644 index 0000000000..62acf388ad --- /dev/null +++ b/MsWheaPkg/Crates/MuTelemetryHelperLib/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "MuTelemetryHelperLib" +version = "0.1.0" +edition = "2021" + +[lib] +name = "mu_telemetry_helper_lib" +path = "src/lib.rs" + +[dependencies] +boot_services = { workspace = true } +mu_pi = { workspace = true } +mu_rust_helpers = { workspace = true } +r-efi = { workspace = true } + +[dev-dependencies] +boot_services = { workspace = true, features = ["mockall"] } +mockall = { version = "0.13.0" } + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin_include)'] } diff --git a/MsWheaPkg/Crates/MuTelemetryHelperLib/src/lib.rs b/MsWheaPkg/Crates/MuTelemetryHelperLib/src/lib.rs new file mode 100644 index 0000000000..826c59cf7c --- /dev/null +++ b/MsWheaPkg/Crates/MuTelemetryHelperLib/src/lib.rs @@ -0,0 +1,268 @@ +//! Rust MU Telemetry Helper +//! +//! Rust helper library for logging telemetry. +//! +//! ## Examples and Usage +//! +//! ```no_run +//! use mu_telemetry_helper_lib::{init_telemetry, log_telemetry}; +//! use r_efi::{efi, system}; +//! pub extern "efiapi" fn efi_main( +//! _image_handle: efi::Handle, +//! system_table: *const system::SystemTable, +//! ) -> efi::Status { +//! +//! //Initialize Boot Services +//! unsafe { +//! init_telemetry((*system_table).boot_services.as_ref().unwrap()); +//! } +//! +//! //if (some_failure) { +//! let _ = log_telemetry(false, 0xA1A2A3A4, 0xB1B2B3B4B5B6B7B8, 0xC1C2C3C4C5C6C7C8, None, None, None); +//! //} +//! +//! efi::Status::SUCCESS +//! } +//! ``` +//! +//! ## License +//! +//! Copyright (C) Microsoft Corporation. + +//! +//! SPDX-License-Identifier: BSD-2-Clause-Patent +//! +#![cfg_attr(target_os = "uefi", no_std)] + +mod status_code_runtime; + +use boot_services::{BootServices, StandardBootServices}; +use mu_pi::{ + protocols::status_code::{EfiStatusCodeType, EfiStatusCodeValue}, + status_code::{EFI_ERROR_CODE, EFI_ERROR_MAJOR, EFI_ERROR_MINOR}, +}; +use mu_rust_helpers::{guid, guid::guid}; +use r_efi::efi; +use status_code_runtime::{ReportStatusCode, StatusCodeRuntimeProtocol}; + +static BOOT_SERVICES: StandardBootServices = StandardBootServices::new_uninit(); + +/// Matches gMsWheaRSCDataTypeGuid in MsWheaPkg\MsWheaPkg.dec +/// Matches MS_WHEA_RSC_DATA_TYPE in MsWheaPkg\Private\Guid\MsWheaReportDataType.h +const MS_WHEA_RSC_DATA_TYPE_GUID: efi::Guid = guid!("91DEEA05-8C0A-4DCD-B91E-F21CA0C68405"); + +const MS_WHEA_ERROR_STATUS_TYPE_INFO: EfiStatusCodeType = EFI_ERROR_MINOR | EFI_ERROR_CODE; +const MS_WHEA_ERROR_STATUS_TYPE_FATAL: EfiStatusCodeType = EFI_ERROR_MAJOR | EFI_ERROR_CODE; + +/** + Internal RSC Extended Data Buffer format used by Project Mu firmware WHEA infrastructure. + + A Buffer of this format should be passed to ReportStatusCodeWithExtendedData + + library_id: GUID of the library reporting the error. If not from a library use zero guid + ihv_sharing_guid: GUID of the partner to share this with. If none use zero guid + additional_info1: 64 bit value used for caller to include necessary interrogative information + additional_info2: 64 bit value used for caller to include necessary interrogative information +**/ +// #pragma pack(1) +// typedef struct { +// EFI_GUID LibraryID; +// EFI_GUID IhvSharingGuid; +// UINT64 AdditionalInfo1; +// UINT64 AdditionalInfo2; +// } MS_WHEA_RSC_INTERNAL_ERROR_DATA; +// #pragma pack() + +#[repr(C)] +struct MsWheaRscInternalErrorData { + library_id: efi::Guid, + ihv_sharing_guid: efi::Guid, + additional_info1: u64, + additional_info2: u64, +} + +/// Log telemetry +/// +/// @param[in] is_fatal This should be set to TRUE if the event will prevent a successful boot. +/// @param[in] class_id An EFI_STATUS_CODE_VALUE representing the event that has occurred. This +/// value will occupy the same space as EventId from LogCriticalEvent(), and +/// should be unique enough to identify a module or region of code. +/// @param[in] extra_data1 [Optional] This should be data specific to the cause. Ideally, used to contain contextual +/// or runtime data related to the event (e.g. register contents, failure codes, etc.). +/// It will be persisted. +/// @param[in] extra_data2 [Optional] Another UINT64 similar to ExtraData1. +/// @param[in] component_id [Optional] This identifier should uniquely identify the module that is emitting this +/// event. When this is passed in as NULL, report status code will automatically populate +/// this field with gEfiCallerIdGuid. +/// @param[in] library_id This should identify the library that is emitting this event. +/// @param[in] ihv_id This should identify the Ihv related to this event if applicable. For example, +/// this would typically be used for TPM and SOC specific events. +#[cfg(not(tarpaulin_include))] +pub fn log_telemetry( + is_fatal: bool, + class_id: EfiStatusCodeValue, + extra_data1: u64, + extra_data2: u64, + component_id: Option<&efi::Guid>, + library_id: Option<&efi::Guid>, + ihv_id: Option<&efi::Guid>, +) -> Result<(), efi::Status> { + log_telemetry_internal( + &BOOT_SERVICES, + is_fatal, + class_id, + extra_data1, + extra_data2, + component_id, + library_id, + ihv_id, + ) +} + +fn log_telemetry_internal( + boot_services: &B, + is_fatal: bool, + class_id: EfiStatusCodeValue, + extra_data1: u64, + extra_data2: u64, + component_id: Option<&efi::Guid>, + library_id: Option<&efi::Guid>, + ihv_id: Option<&efi::Guid>, +) -> Result<(), efi::Status> { + let status_code_type: EfiStatusCodeType = + if is_fatal { MS_WHEA_ERROR_STATUS_TYPE_FATAL } else { MS_WHEA_ERROR_STATUS_TYPE_INFO }; + + let error_data = MsWheaRscInternalErrorData { + library_id: *library_id.unwrap_or(&guid::ZERO), + ihv_sharing_guid: *ihv_id.unwrap_or(&guid::ZERO), + additional_info1: extra_data1, + additional_info2: extra_data2, + }; + + StatusCodeRuntimeProtocol::report_status_code( + boot_services, + status_code_type, + class_id, + 0, + component_id, + MS_WHEA_RSC_DATA_TYPE_GUID, + error_data, + ) +} + +#[cfg(not(tarpaulin_include))] +pub fn init_telemetry(efi_boot_services: &efi::BootServices) { + BOOT_SERVICES.initialize(efi_boot_services) +} + +#[cfg(test)] +mod test { + use boot_services::MockBootServices; + use mu_pi::protocols::{ + status_code, + status_code::{EfiStatusCodeData, EfiStatusCodeType, EfiStatusCodeValue}, + }; + use mu_rust_helpers::guid::guid; + use r_efi::efi; + + use crate::{ + log_telemetry_internal, status_code_runtime::StatusCodeRuntimeProtocol, MsWheaRscInternalErrorData, + MS_WHEA_ERROR_STATUS_TYPE_FATAL, + }; + use core::mem::size_of; + + const DATA_SIZE: usize = size_of::() + size_of::(); + const MOCK_CALLER_ID: efi::Guid = guid!("d0d1d2d3-d4d5-d6d7-d8d9-dadbdcdddedf"); + const MOCK_STATUS_CODE_VALUE: EfiStatusCodeValue = 0xa0a1a2a3; + + extern "efiapi" fn mock_report_status_code( + r#type: EfiStatusCodeType, + value: EfiStatusCodeValue, + instance: u32, + caller_id: *const efi::Guid, // Optional + _data: *const EfiStatusCodeData, // Optional + ) -> efi::Status { + assert_eq!(value, MOCK_STATUS_CODE_VALUE); + assert_eq!(instance, 0); + assert_eq!(unsafe { *caller_id }, MOCK_CALLER_ID); + if r#type == MS_WHEA_ERROR_STATUS_TYPE_FATAL { + efi::Status::SUCCESS + } else { + efi::Status::INVALID_PARAMETER + } + } + + static MOCK_STATUS_CODE_RUNTIME_INTERFACE: status_code::Protocol = + status_code::Protocol { report_status_code: mock_report_status_code }; + + #[test] + fn try_log_telemetry() { + let mut mock_boot_services: MockBootServices = MockBootServices::new(); + + mock_boot_services.expect_locate_protocol().returning(|_: &StatusCodeRuntimeProtocol, registration| unsafe { + assert_eq!(registration, None); + Ok(Some( + (&MOCK_STATUS_CODE_RUNTIME_INTERFACE as *const status_code::Protocol as *mut status_code::Protocol) + .as_mut() + .unwrap(), + )) + }); + + // Test sizes of "repr(C)" structs + assert_eq!(size_of::(), 48); + assert_eq!(size_of::<[u8; 68]>(), DATA_SIZE); + + // Test Deref trait + assert_eq!(*StatusCodeRuntimeProtocol, status_code::PROTOCOL_GUID); + assert_eq!( + Ok(()), + log_telemetry_internal( + &mock_boot_services, + true, + MOCK_STATUS_CODE_VALUE, + 0xb0b1b2b3b4b5b6b7, + 0xc0c1c2c3c4c5c6c7, + Some(&MOCK_CALLER_ID), + Some(&guid!("e0e1e2e3-e4e5-e6e7-e8e9-eaebecedeeef")), + Some(&guid!("f0f1f2f3-f4f5-f6f7-f8f9-fafbfcfdfeff")) + ) + ); + assert_eq!( + Err(efi::Status::INVALID_PARAMETER), + log_telemetry_internal( + &mock_boot_services, + false, + MOCK_STATUS_CODE_VALUE, + 0xb0b1b2b3b4b5b6b7, + 0xc0c1c2c3c4c5c6c7, + Some(&MOCK_CALLER_ID), + None, + None + ) + ); + } + + #[test] + fn test_protocol_not_found() { + let mut mock_boot_services: MockBootServices = MockBootServices::new(); + + mock_boot_services.expect_locate_protocol().returning(|_: &StatusCodeRuntimeProtocol, registration| { + assert_eq!(registration, None); + //Simulate "marker protocol" without an Interface + Ok(None) + }); + assert_eq!( + Err(efi::Status::NOT_FOUND), + log_telemetry_internal( + &mock_boot_services, + false, + MOCK_STATUS_CODE_VALUE, + 0xb0b1b2b3b4b5b6b7, + 0xc0c1c2c3c4c5c6c7, + Some(&MOCK_CALLER_ID), + None, + None + ) + ); + } +} diff --git a/MsWheaPkg/Crates/MuTelemetryHelperLib/src/status_code_runtime.rs b/MsWheaPkg/Crates/MuTelemetryHelperLib/src/status_code_runtime.rs new file mode 100644 index 0000000000..5f146c55d5 --- /dev/null +++ b/MsWheaPkg/Crates/MuTelemetryHelperLib/src/status_code_runtime.rs @@ -0,0 +1,85 @@ +extern crate alloc; + +use core::{mem, ops::Deref, slice}; + +use alloc::vec::Vec; +use boot_services::{protocol_handler::Protocol, BootServices}; +use mu_pi::protocols::{ + status_code, + status_code::{EfiStatusCodeData, EfiStatusCodeType, EfiStatusCodeValue}, +}; +use mu_rust_helpers::guid; +use r_efi::efi; + +pub struct StatusCodeRuntimeProtocol; + +impl Deref for StatusCodeRuntimeProtocol { + type Target = efi::Guid; + + fn deref(&self) -> &Self::Target { + self.protocol_guid() + } +} + +unsafe impl Protocol for StatusCodeRuntimeProtocol { + type Interface = status_code::Protocol; + + fn protocol_guid(&self) -> &'static efi::Guid { + &status_code::PROTOCOL_GUID + } +} + +unsafe fn any_as_u8_slice(p: &T) -> &[u8] { + slice::from_raw_parts((p as *const T) as *const u8, mem::size_of::()) +} + +/// Rust interface for Report Status Code +pub trait ReportStatusCode { + fn report_status_code( + boot_services: &B, + status_code_type: EfiStatusCodeType, + status_code_value: EfiStatusCodeValue, + instance: u32, + caller_id: Option<&efi::Guid>, + data_type: efi::Guid, + data: T, + ) -> Result<(), efi::Status>; +} + +impl ReportStatusCode for StatusCodeRuntimeProtocol { + fn report_status_code( + boot_services: &B, + status_code_type: EfiStatusCodeType, + status_code_value: EfiStatusCodeValue, + instance: u32, + caller_id: Option<&efi::Guid>, + data_type: efi::Guid, + data: T, + ) -> Result<(), efi::Status> { + let protocol = boot_services.locate_protocol(&StatusCodeRuntimeProtocol, None)?; + if protocol.is_none() { + return Err(efi::Status::NOT_FOUND); + } + + let header_size = mem::size_of::(); + let data_size = mem::size_of::(); + + let header = EfiStatusCodeData { header_size: header_size as u16, size: data_size as u16, r#type: data_type }; + + let mut data_buffer = Vec::from(unsafe { any_as_u8_slice(&header) }); + data_buffer.extend(unsafe { any_as_u8_slice(&data) }); + + let data_ptr: *mut EfiStatusCodeData = data_buffer.as_mut_ptr() as *mut EfiStatusCodeData; + + let caller_id = caller_id.or(Some(&guid::CALLER_ID)).unwrap(); + + let status = + (protocol.unwrap().report_status_code)(status_code_type, status_code_value, instance, caller_id, data_ptr); + + if status.is_error() { + Err(status) + } else { + Ok(()) + } + } +}