diff --git a/rust/edlcodegen/crates/samples/helloworld/Cargo.toml b/rust/edlcodegen/crates/samples/helloworld/Cargo.toml index cf3238f9..e90029a9 100644 --- a/rust/edlcodegen/crates/samples/helloworld/Cargo.toml +++ b/rust/edlcodegen/crates/samples/helloworld/Cargo.toml @@ -16,3 +16,7 @@ windows = "0.62.2" # TODO: once edlcodegen-tools is published we should be able to use the version in crates.io edlcodegen-tools = { git = "https://github.com/microsoft/VbsEnclaveTooling" } +tracelogging = { version = "1.2.4", default-features = false } +vbsenclave-sdk-enclave = { path = "../../../../sdk/crates/libs/sdk-enclave" } +vbsenclave-sdk-host = { path = "../../../../sdk/crates/libs/sdk-host" } +windows-enclave = { path = "../../../../sdk/crates/libs/windows-enclave" } diff --git a/rust/edlcodegen/crates/samples/helloworld/enclave/Cargo.toml b/rust/edlcodegen/crates/samples/helloworld/enclave/Cargo.toml index 0680e98e..32ff9173 100644 --- a/rust/edlcodegen/crates/samples/helloworld/enclave/Cargo.toml +++ b/rust/edlcodegen/crates/samples/helloworld/enclave/Cargo.toml @@ -13,3 +13,6 @@ edlcodegen-tools = { workspace = true } [dependencies] # Note: To generate crate run rust/crates/samples/helloworld/generate_edl_crates.ps1 at least once test_enclave_gen = {path = "generated/test_gen" } +tracelogging = { workspace = true, features = ["macros", "etw", "kernel_mode"]} +vbsenclave-sdk-enclave = { workspace = true } +windows-enclave = { workspace = true } \ No newline at end of file diff --git a/rust/edlcodegen/crates/samples/helloworld/enclave/src/edl_impls.rs b/rust/edlcodegen/crates/samples/helloworld/enclave/src/edl_impls.rs index bdfe5ab4..d22a0a6a 100644 --- a/rust/edlcodegen/crates/samples/helloworld/enclave/src/edl_impls.rs +++ b/rust/edlcodegen/crates/samples/helloworld/enclave/src/edl_impls.rs @@ -6,11 +6,20 @@ use test_enclave_gen::implementation::trusted::Trusted; use test_enclave_gen::AbiError; use test_enclave_gen::stubs::untrusted::print; use alloc::string::ToString; +use tracelogging as tlg; +use crate::{HELLO_WORLD_PROVIDER}; pub struct EnclaveImpl{} impl Trusted for EnclaveImpl { fn do_secret_math(val1: u32, val2: u32) -> Result { + tlg::write_event!( + HELLO_WORLD_PROVIDER, // The provider to use for the event. + "do_secret_math_invoked", // Event category bits. + str8("Field1", "do_secret_math invoked!"), // Add a string field to the event. + ); + + // Print message to console from within the enclave. print(&"Performing secret math operation inside VTL1...".to_string())?; Ok(val1 + val2) } diff --git a/rust/edlcodegen/crates/samples/helloworld/enclave/src/enclave_constants.rs b/rust/edlcodegen/crates/samples/helloworld/enclave/src/enclave_constants.rs deleted file mode 100644 index bdcce213..00000000 --- a/rust/edlcodegen/crates/samples/helloworld/enclave/src/enclave_constants.rs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -// Temporary: The contents of this file should be provided by the future -// windows-enclave crate. For now, we define the necessary constants and -// structs here. - -#[allow(dead_code)] // debug policy not used in release builds -pub const IMAGE_ENCLAVE_POLICY_DEBUGGABLE: u32 = 0x0000_0001; -pub const IMAGE_ENCLAVE_SHORT_ID_LENGTH: usize = 16; -pub const IMAGE_ENCLAVE_FLAG_PRIMARY_IMAGE: u32 = 0x0000_0001; - -// See: https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_enclave_config64 -#[repr(C)] -pub struct ImageEnclaveConfig { - pub size: u32, - pub minimum_required_config_size: u32, - pub policy_flags: u32, - pub number_of_imports: u32, - pub import_list: u32, - pub import_entry_size: u32, - pub family_id: [u8; IMAGE_ENCLAVE_SHORT_ID_LENGTH], - pub image_id: [u8; IMAGE_ENCLAVE_SHORT_ID_LENGTH], - pub image_version: u32, - pub security_version: u32, - pub enclave_size: usize, - pub number_of_threads: u32, - pub enclave_flags: u32, -} - -pub const IMAGE_ENCLAVE_MINIMUM_CONFIG_SIZE: u32 = - core::mem::offset_of!(ImageEnclaveConfig, enclave_flags) as u32; diff --git a/rust/edlcodegen/crates/samples/helloworld/enclave/src/lib.rs b/rust/edlcodegen/crates/samples/helloworld/enclave/src/lib.rs index ce602b82..f92ddd82 100644 --- a/rust/edlcodegen/crates/samples/helloworld/enclave/src/lib.rs +++ b/rust/edlcodegen/crates/samples/helloworld/enclave/src/lib.rs @@ -4,9 +4,11 @@ #![no_std] extern crate alloc; mod edl_impls; -mod enclave_constants; -use enclave_constants::*; -use core::panic::PanicInfo; +use core::panic::{ PanicInfo}; +use tracelogging as tlg; +use windows_enclave::vertdll::{ + IMAGE_ENCLAVE_FLAG_PRIMARY_IMAGE, ImageEnclaveConfig, IMAGE_ENCLAVE_MINIMUM_CONFIG_SIZE +}; // Developer creates their own panic handler for no_std environments. // Note: The rust analyzer in VSCode shows red squiggly lines in the IDE. @@ -23,10 +25,14 @@ fn panic(_panic: &PanicInfo<'_>) -> ! { // test_enclave_gen crate. test_enclave_gen::export_enclave_functions!(edl_impls::EnclaveImpl); +// Export the SDK enclave functions using the macro from the +// vbsenclave_sdk_enclave crate. +vbsenclave_sdk_enclave::export_sdk_enclave_functions!(); + pub const ENCLAVE_CONFIG_POLICY_FLAGS: u32 = { // Enable debuggability of the enclave in debug builds. #[cfg(debug_assertions)] - { IMAGE_ENCLAVE_POLICY_DEBUGGABLE } + { windows_enclave::vertdll::IMAGE_ENCLAVE_POLICY_DEBUGGABLE } // Disable debuggability in release builds. #[cfg(not(debug_assertions))] @@ -55,11 +61,22 @@ pub static __enclave_config: ImageEnclaveConfig = ImageEnclaveConfig { enclave_flags: IMAGE_ENCLAVE_FLAG_PRIMARY_IMAGE, }; +tlg::define_provider!( + HELLO_WORLD_PROVIDER, + "HelloWorldProvider", +); + #[unsafe(no_mangle)] #[allow(non_snake_case)] pub extern "system" fn DllMain( _instance: *const core::ffi::c_void, - _reason: u32, + reason: u32, _reserved: *mut core::ffi::c_void,) -> bool { + + // DLL_PROCESS_ATTACH + if reason == 1 { + vbsenclave_sdk_enclave::etw::add_provider(&HELLO_WORLD_PROVIDER); + } + true } \ No newline at end of file diff --git a/rust/edlcodegen/crates/samples/helloworld/host/Cargo.toml b/rust/edlcodegen/crates/samples/helloworld/host/Cargo.toml index 1dc6e63e..1872f47b 100644 --- a/rust/edlcodegen/crates/samples/helloworld/host/Cargo.toml +++ b/rust/edlcodegen/crates/samples/helloworld/host/Cargo.toml @@ -8,3 +8,5 @@ windows = { workspace = true, features = ["Win32_System_Threading"] } # Note: To generate crate run rust/crates/samples/helloworld/generate_edl_crates.ps1 at least once test_host_gen = {path = "generated/test_gen" } +tracelogging = { workspace = true, features = ["macros", "etw"]} +vbsenclave-sdk-host = { workspace = true } diff --git a/rust/edlcodegen/crates/samples/helloworld/host/src/container.rs b/rust/edlcodegen/crates/samples/helloworld/host/src/container.rs deleted file mode 100644 index 17af9620..00000000 --- a/rust/edlcodegen/crates/samples/helloworld/host/src/container.rs +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use windows::core::{HRESULT, Error, Result}; -use windows::Win32::Foundation::TRUE; -use crate::enclave_api_ffi::*; -use windows::Win32::System::Threading::GetCurrentProcess; -use core::ffi::c_void; - -/// Temporary container for managing the lifecycle of the VBS enclave. -/// Once the Vbs enclave sdk crate is available, this can be replaced with -/// the use of lifecycle APIs from that crate. -pub struct EnclaveContainer { - pub enclave: *mut c_void, -} - -impl EnclaveContainer { - pub fn new() -> Result { - let mut enclave: *mut c_void = core::ptr::null_mut(); - - let mut creation_attempt = || -> Result<()> { - if unsafe { !IsEnclaveTypeSupported(ENCLAVE_TYPE_VBS) }.as_bool() { - return Err(Error::new( - HRESULT::from_thread(), - "VBS Enclaves not supported".to_string(), - )); - } - - // Construct ENCLAVE_CREATE_INFO_VBS with arbitrary owner ID for this sample. - // See: https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-enclave_create_info_vbs - let create_info: ENCLAVE_CREATE_INFO_VBS = ENCLAVE_CREATE_INFO_VBS { - Flags: ENCLAVE_CREATE_INFO_FLAG, - OwnerID: [ - 0x10, 0x20, 0x30, 0x40, - 0x41, 0x31, 0x21, 0x11, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - ], - }; - - let info_ptr = &raw const create_info as *const c_void; - - enclave = unsafe { - CreateEnclave( - GetCurrentProcess().0, - core::ptr::null_mut(), - 0x10000000, - 0, - ENCLAVE_TYPE_VBS, - info_ptr, - ENCLAVE_CREATE_INFO_VBS_SIZE, - core::ptr::null_mut() as *mut u32, - ) - }; - - if enclave.is_null() { - return Err(Error::new( - HRESULT::from_thread(), - "Couldn't create enclave".to_string(), - )); - } - - unsafe { - if !LoadEnclaveImageW(enclave, windows::core::w!("enclave.dll")).as_bool() { - return Err(Error::new( - HRESULT::from_thread(), - "Couldn't load enclave".to_string(), - )); - } - } - - let mut init_info = ENCLAVE_INIT_INFO_VBS::default(); - init_info.Length = ENCLAVE_INIT_INFO_VBS_SIZE; - init_info.ThreadCount = 1; - - unsafe { - if !InitializeEnclave( - GetCurrentProcess().0, - enclave, - &init_info as *const _ as _, - init_info.Length, - core::ptr::null_mut() as *mut u32, - ).as_bool() { - return Err(Error::new( - HRESULT::from_thread(), - "Couldn't initialize enclave".to_string(), - )); - } - } - - Ok(()) - }; - - if let Err(e) = creation_attempt() { - unsafe { Self::unload_enclave(enclave) }; - return Err(e); - } - - Ok(Self { enclave }) - } - - unsafe fn unload_enclave(enclave: *mut c_void) { - if !enclave.is_null() { - unsafe { - if !TerminateEnclave(enclave, TRUE).as_bool() { - print_unload_error("Failed to terminate enclave before deletion", HRESULT::from_thread()); - return; - } - - if !DeleteEnclave(enclave).as_bool() { - print_unload_error("Failed to delete enclave", HRESULT::from_thread()); - } - } - } - } -} - -impl Drop for EnclaveContainer { - fn drop(&mut self) { - unsafe { - Self::unload_enclave(self.enclave); - } - } -} - -fn print_unload_error(err: &str, hresult: HRESULT) { - eprintln!("{}: HRESULT 0x{:08x}", err, hresult.0); -} \ No newline at end of file diff --git a/rust/edlcodegen/crates/samples/helloworld/host/src/enclave_api_ffi.rs b/rust/edlcodegen/crates/samples/helloworld/host/src/enclave_api_ffi.rs deleted file mode 100644 index 1784dae2..00000000 --- a/rust/edlcodegen/crates/samples/helloworld/host/src/enclave_api_ffi.rs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#![allow(non_snake_case, non_camel_case_types)] -use core::ffi::c_void; - -// Once the windows-enclave crate is available in crates.io, the windows-rs crate -// will likely have support for these APIs in their correct VTL0 dll location. -// For now, we need to manually link the required functions here since using -// the windows::Win32::System::Environment module in the windows crate links -// TerminateEnclave from vertdll. -// See: https://github.com/microsoft/windows-rs/blob/23ec2a2267646dbb87658f1b1247eae0ec49a9e7/crates/libs/windows/src/Windows/Win32/System/Environment/mod.rs#L236C26-L236C38 -windows::core::link!("api-ms-win-core-enclave-l1-1-1.dll" "system" fn TerminateEnclave(lpaddress : *const c_void, fwait : windows::core::BOOL) -> windows::core::BOOL); -windows::core::link!("api-ms-win-core-enclave-l1-1-1.dll" "system" fn DeleteEnclave(lpaddress : *const c_void) -> windows::core::BOOL); -windows::core::link!("api-ms-win-core-enclave-l1-1-1.dll" "system" fn LoadEnclaveImageW(lpenclaveaddress : *const c_void, lpimagename : windows::core::PCWSTR) -> windows::core::BOOL); -windows::core::link!("api-ms-win-core-enclave-l1-1-0.dll" "system" fn InitializeEnclave(hprocess : *mut c_void, lpaddress : *const c_void, lpenclaveinformation : *const c_void, dwinfolength : u32, lpenclaveerror : *mut u32) -> windows::core::BOOL); -windows::core::link!("api-ms-win-core-enclave-l1-1-0.dll" "system" fn IsEnclaveTypeSupported(flenclavetype : u32) -> windows::core::BOOL); -windows::core::link!("api-ms-win-core-enclave-l1-1-0.dll" "system" fn CreateEnclave(hprocess : *mut c_void , lpaddress : *const c_void, dwsize : usize, dwinitialcommitment : usize, flenclavetype : u32, lpenclaveinformation : *const c_void, dwinfolength : u32, lpenclaveerror : *mut u32) -> *mut c_void); - -#[repr(C)] -#[derive(Default)] -pub struct ENCLAVE_INIT_INFO_VBS { - pub Length: u32, - pub ThreadCount: u32, -} - -pub const ENCLAVE_INIT_INFO_VBS_SIZE: u32 = core::mem::size_of::() as u32; - -#[repr(C)] -pub struct ENCLAVE_CREATE_INFO_VBS { - pub Flags: u32, - pub OwnerID: [u8; 32], -} - -pub const ENCLAVE_CREATE_INFO_VBS_SIZE: u32 = core::mem::size_of::() as u32; - -pub const ENCLAVE_TYPE_VBS: u32 = 0x0000_0010; - -pub const ENCLAVE_CREATE_INFO_FLAG: u32 = { - // enable debug in debug builds - #[cfg(debug_assertions)] - { 0x0000_0001 } - - // disable debug in release builds - #[cfg(not(debug_assertions))] - { 0x0000_0000 } -}; \ No newline at end of file diff --git a/rust/edlcodegen/crates/samples/helloworld/host/src/main.rs b/rust/edlcodegen/crates/samples/helloworld/host/src/main.rs index 6d649753..a2588ac2 100644 --- a/rust/edlcodegen/crates/samples/helloworld/host/src/main.rs +++ b/rust/edlcodegen/crates/samples/helloworld/host/src/main.rs @@ -3,25 +3,43 @@ use test_host_gen::stubs::trusted::TestVtl0Host; mod edl_impls; -mod enclave_api_ffi; -mod container; -use crate::container::EnclaveContainer; use crate::edl_impls::HostImpl; +use vbsenclave_sdk_host::enclave::EnclaveHandle; +use vbsenclave_sdk_host::enclave; + +const ENCLAVE_CREATE_INFO_FLAG: u32 = { + #[cfg(debug_assertions)] + { vbsenclave_sdk_host::enclave::ENCLAVE_CREATE_INFO_FLAG_DEBUG } + + #[cfg(not(debug_assertions))] + { vbsenclave_sdk_host::enclave::ENCLAVE_CREATE_INFO_FLAG_RELEASE } +}; fn main() -> Result<(), Box> { println!("Starting VBS Enclave HelloWorld Sample..."); - let enclave_container = EnclaveContainer::new()?; - let test_host = TestVtl0Host::new(enclave_container.enclave); + let enclave_handle = EnclaveHandle::create_and_initialize( + "enclave.dll", + enclave::megabytes(256), + None, + ENCLAVE_CREATE_INFO_FLAG, + )?; + + let test_host = TestVtl0Host::new(enclave_handle.as_ptr()); + // Register the untrusted function implementations for the test enclave. if let Err(err) = test_host.register_vtl0_callbacks::() { - panic!("Failed to register VTL0 callbacks: HRESULT: {:x}", err.to_hresult().0); + return Err(format!("Failed to register VTL0 callbacks: HRESULT: {:x}", err.to_hresult().0).into()); + } + + // Register SDK VTL0 callbacks to enable SDK features in the enclave. + if let Err(err) = vbsenclave_sdk_host::register_sdk_callbacks(enclave_handle.as_ptr()) { + return Err(format!("Failed to register SDK VTL0 callbacks: HRESULT: {:x}", err.to_hresult().0).into()); } let secret_result = test_host.do_secret_math(42, 58); if let Err(err) = secret_result { - panic!("do_secret_math failed: HRESULT {:x}", err.to_hresult().0); - + return Err(format!("do_secret_math failed: HRESULT {:x}", err.to_hresult().0).into()); } let secret_value = secret_result.unwrap(); diff --git a/rust/scripts/generate_codegen_crates.ps1 b/rust/scripts/generate_codegen_crates.ps1 index 6aeb05f0..123322f1 100644 --- a/rust/scripts/generate_codegen_crates.ps1 +++ b/rust/scripts/generate_codegen_crates.ps1 @@ -32,6 +32,11 @@ $ErrorActionPreference = "Stop" --OutputDirectory $EnclaveOutDir ` --ImportDirectories $ImportDirectories +if ($LASTEXITCODE -ne 0) { + # The exe prints out the error code and text on failure. + throw "EdlCodegen failed to generate the enclave crate" +} + # Run codegen for the host crate & $edlCodeGenToolsPath ` --namespace $Namespace ` @@ -41,3 +46,8 @@ $ErrorActionPreference = "Stop" --Vtl0ClassName $Vtl0ClassName ` --OutputDirectory $HostAppOutDir ` --ImportDirectories $ImportDirectories + +if ($LASTEXITCODE -ne 0) { + # The exe prints out the error code and text on failure. + throw "EdlCodegen failed to generate the host crate" +} diff --git a/rust/sdk/Cargo.toml b/rust/sdk/Cargo.toml index 5415ef8b..eebf5a33 100644 --- a/rust/sdk/Cargo.toml +++ b/rust/sdk/Cargo.toml @@ -17,7 +17,9 @@ edition = "2024" edlcodegen-tools = { git = "https://github.com/microsoft/VbsEnclaveTooling" } regex = "1.11" spin = "0.10.0" +tracelogging = { version ="1.2.4", default-features = false } userboundkey-kcm = { version = "0.1.0", path = "crates/libs/userboundkey-kcm" } +uuid = {version = "1.19.0", default-features = false } vbsenclave-sdk-enclave = { version = "0.1.0", path = "crates/libs/sdk-enclave" } vbsenclave-sdk-host = { version = "0.1.0", path = "crates/libs/sdk-host" } widestring = { version = "1.1", default-features = false, features = ["alloc"] } diff --git a/rust/sdk/crates/etw.md b/rust/sdk/crates/etw.md new file mode 100644 index 00000000..ae9a1792 --- /dev/null +++ b/rust/sdk/crates/etw.md @@ -0,0 +1,112 @@ +# ETW Support for VBS Enclaves + +The SDK enables the use of the [Tracelogging Rust Crate](https://crates.io/crates/tracelogging) +inside VBS Enclaves. When providers are registered with the SDK, it manages +provider registration and deregistration, and transparently forwards ETW calls between the +enclave and the host. No additional integration steps are required. + +> [!Note] +> This document explains how to enable TraceLogging within enclaves. +> Using `TraceLogging` on the host is straightforward: add the `tracelogging` crate +> as a dependency and follow the public example and documentation +> [here](https://docs.rs/tracelogging/1.2.4/tracelogging/#example). +> No dependency on any of our crates is required for host-side TraceLogging. + +## Inside your Enclave + +### 1. Add dependencies + +```toml +[dependencies] +tracelogging = { version = "1.2.4", features = ["macros", "etw", "kernel_mode"] } +vbsenclave-sdk-enclave = "0.1.0" +``` + +### 2. Define an ETW provider + +```rust +// Use the tracelogging crate inside your enclave. +// See: +use tracelogging; + +tracelogging::define_provider!( + HELLO_WORLD_PROVIDER, + "HelloWorldProvider", +); + +// Now you can use the Tracelogging crates `write_event` macro to send events using +// that provider throughout your codebase! +``` + +### 3. Register the provider at DLL load + +Register SDK exports and add the provider in your `DllMain`: + +```rust +use core::ffi; + +// Export SDK enclave functions at least once using the +// export_sdk_enclave_functions macro. Usually in your lib.rs. +vbsenclave_sdk_enclave::export_sdk_enclave_functions!(); + +#[unsafe(no_mangle)] +pub extern "system" fn DllMain(instance: *const c_void, reason: u32, reserved: *mut c_void, +) -> bool { + + // DLL_PROCESS_ATTACH + if reason == 1 { + // Add a provider or list of providers. + vbsenclave_sdk_enclave::etw::add_provider(&HELLO_WORLD_PROVIDER); + } + + true +} +``` + +## Inside your Host + +### 1. Add dependencies + +```toml +[dependencies] +vbsenclave-sdk-host = "0.1.0" +``` + +### 2. Register SDK callbacks after enclave is loaded and initialized + +```rust +use vbsenclave_sdk_host::enclave::EnclaveHandle; +use vbsenclave_sdk_host::enclave; + +let enclave_handle = EnclaveHandle::create_and_initialize( + "enclave.dll", + enclave::megabytes(256), + None, + 0 +)?; + +// Call at least once after enclave initialization. +vbsenclave_sdk_host::register_sdk_callbacks(enclave_handle.as_ptr())?; +``` + +### 3. Unregister providers during teardown + +> [!Note] +> When an enclave is created or owned via the SDK's `EnclaveHandle` smart pointer, +> ETW providers are automatically unregistered as part of enclave teardown. +> Otherwise, you will need to call `unregister_etw_providers` manually like below. + +Before terminating and deleting the enclave: + +```rust +vbsenclave_sdk_host::unregister_etw_providers(enclave)?; +``` + +The HelloWorld sample [here](https://github.com/microsoft/VbsEnclaveTooling/tree/main/rust/edlcodegen/crates/samples/helloworld) +demonstrates this usage. + +## Viewing trace events +Developers can collect trace events using the [TraceView](https://learn.microsoft.com/en-us/windows-hardware/drivers/devtest/traceview) graphical user +interface or by using the [Tracelog](https://learn.microsoft.com/en-us/windows-hardware/drivers/devtest/tracelog) command-line +interface. Both applications can be found in the latest +release of the [Windows SDK](https://learn.microsoft.com/en-us/windows/apps/windows-sdk/downloads). \ No newline at end of file diff --git a/rust/sdk/crates/libs/sdk.edl b/rust/sdk/crates/libs/edl/sdk/sdk.edl similarity index 97% rename from rust/sdk/crates/libs/sdk.edl rename to rust/sdk/crates/libs/edl/sdk/sdk.edl index 80661570..6f3e184d 100644 --- a/rust/sdk/crates/libs/sdk.edl +++ b/rust/sdk/crates/libs/edl/sdk/sdk.edl @@ -6,6 +6,8 @@ enclave { + import "tracelogging.edl"; + // Configuration for credential caching behavior struct keyCredentialCacheConfig { @@ -95,5 +97,7 @@ enclave void userboundkey_delete_credential( uint64_t credential ); + + void println(string msg); }; }; diff --git a/rust/sdk/crates/libs/edl/sdk/tracelogging.edl b/rust/sdk/crates/libs/edl/sdk/tracelogging.edl new file mode 100644 index 00000000..a9f89737 --- /dev/null +++ b/rust/sdk/crates/libs/edl/sdk/tracelogging.edl @@ -0,0 +1,78 @@ +enclave +{ + struct Guid { + uint32_t data1; + uint16_t data2; + uint16_t data3; + uint8_t data4 [8]; + }; + + struct EventDataDescriptor { + vector descriptor; + uint32_t reserved; + }; + + struct EventFilterDescriptor { + vector descriptor; + uint32_t type_data; + }; + + struct EventDescriptor { + uint16_t id; + uint8_t version; + uint8_t channel; + uint8_t level; + uint8_t opcode; + uint16_t task; + uint64_t keyword; + }; + + trusted + { + void register_etw_providers(); + void unregister_etw_providers(); + + void etw_callback_passthrough( + Guid registration_id, + Guid source_id, + uint32_t event_control_code, + uint8_t level, + uint64_t match_any_keyword, + uint64_t match_all_keyword, + EventFilterDescriptor filter_data, + ); + }; + + untrusted + { + uint32_t event_unregister ( + uintptr_t reg_handle + ); + + uint32_t event_register( + Guid provider_id, + Guid registration_id, + uintptr_t enclave, + [out] uintptr_t reg_handle + ); + + uint32_t event_set_information( + uintptr_t reg_handle, + uint32_t information_class, + vector information, + ); + + uint32_t event_write_transfer( + uintptr_t reg_handle, + EventDescriptor descriptor, + optional activity_id, + optional related_id, + vector user_data, + ); + + uint32_t event_activity_id_control( + uint32_t control_code, + [in, out] Guid activity_id + ); + }; +} \ No newline at end of file diff --git a/rust/sdk/crates/libs/sdk-enclave/Cargo.toml b/rust/sdk/crates/libs/sdk-enclave/Cargo.toml index 5ff4b67a..d54eb525 100644 --- a/rust/sdk/crates/libs/sdk-enclave/Cargo.toml +++ b/rust/sdk/crates/libs/sdk-enclave/Cargo.toml @@ -6,5 +6,8 @@ edition = { workspace = true } [dependencies] sdk_enclave_gen = { path = "./generated/sdk_gen" } spin = { workspace = true } +tracelogging = { workspace = true, features = ["etw", "kernel_mode"] } +uuid = { workspace = true, default-features = false, features = [ "v4"] } widestring = { workspace = true } -windows-enclave = { workspace = true } +windows-enclave = {workspace = true } + diff --git a/rust/sdk/crates/libs/sdk-enclave/src/common/sdk_impls.rs b/rust/sdk/crates/libs/sdk-enclave/src/common/sdk_impls.rs index 0ea9319b..3abe0ef3 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/common/sdk_impls.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/common/sdk_impls.rs @@ -8,12 +8,13 @@ //! This pattern allows feature implementations to live in their own modules //! while the trait impl acts as a thin wrapper. +use crate::etw; +use crate::userboundkey; use alloc::vec::Vec; use sdk_enclave_gen::AbiError; use sdk_enclave_gen::implementation::trusted::Trusted; use sdk_enclave_gen::implementation::types::attestationReportAndSessionInfo; - -use crate::userboundkey; +use sdk_enclave_gen::implementation::types::{EventFilterDescriptor, Guid}; /// SDK enclave implementation of the Trusted trait. /// @@ -32,4 +33,34 @@ impl Trusted for EnclaveImpl { fn userboundkey_close_session(sessionInfo: u64) -> Result<(), AbiError> { userboundkey::userboundkey_close_session(sessionInfo) } + + fn register_etw_providers() -> Result<(), AbiError> { + etw::register_providers(); + Ok(()) + } + + fn unregister_etw_providers() -> Result<(), AbiError> { + etw::unregister_providers(); + Ok(()) + } + + fn etw_callback_passthrough( + registration_id: &Guid, + source_id: &Guid, + event_control_code: u32, + level: u8, + match_any_keyword: u64, + match_all_keyword: u64, + filter_data: &EventFilterDescriptor, + ) -> Result<(), AbiError> { + etw::etw_callback_passthrough( + registration_id, + source_id, + event_control_code, + level, + match_any_keyword, + match_all_keyword, + filter_data, + ) + } } diff --git a/rust/sdk/crates/libs/sdk-enclave/src/common/utils.rs b/rust/sdk/crates/libs/sdk-enclave/src/common/utils.rs index 9f73b455..c1fdc070 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/common/utils.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/common/utils.rs @@ -12,6 +12,7 @@ use spin::Once; use windows_enclave::vertdll::{ ENCLAVE_FLAG_DYNAMIC_DEBUG_ACTIVE, ENCLAVE_FLAG_DYNAMIC_DEBUG_ENABLED, ENCLAVE_FLAG_FULL_DEBUG_ENABLED, ENCLAVE_INFORMATION, EnclaveGetEnclaveInformation, + RtlNtStatusToDosError, }; /// Common error type for enclave utility operations @@ -112,3 +113,26 @@ pub fn is_enclave_dynamic_debug_active() -> Result { let info = get_enclave_information()?; Ok((info.Identity.Flags & ENCLAVE_FLAG_DYNAMIC_DEBUG_ACTIVE) != 0) } + +/// Converts a Win32 error code to an HRESULT. +/// Equivalent to the HRESULT_FROM_WIN32 macro. +#[inline] +pub fn hresult_from_win32(error: u32) -> i32 { + if error == 0 { + 0 // S_OK + } else { + // FACILITY_WIN32 = 7, so (7 << 16) | 0x80000000 = 0x80070000 + ((error & 0x0000FFFF) | 0x80070000) as i32 + } +} + +/// Convert NTSTATUS to HRESULT using RtlNtStatusToDosError +pub fn ntstatus_to_hresult(ntstatus: i32) -> i32 { + // SAFETY: RtlNtStatusToDosError is a pure function that converts NTSTATUS to Win32 error + let win32_error = unsafe { RtlNtStatusToDosError(ntstatus) }; + if win32_error == 0 { + 0 // S_OK + } else { + hresult_from_win32(win32_error) + } +} diff --git a/rust/sdk/crates/libs/sdk-enclave/src/enclave_ffi.rs b/rust/sdk/crates/libs/sdk-enclave/src/enclave_ffi.rs deleted file mode 100644 index fc36ab24..00000000 --- a/rust/sdk/crates/libs/sdk-enclave/src/enclave_ffi.rs +++ /dev/null @@ -1,2 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. diff --git a/rust/sdk/crates/libs/sdk-enclave/src/etw.rs b/rust/sdk/crates/libs/sdk-enclave/src/etw.rs new file mode 100644 index 00000000..0897f634 --- /dev/null +++ b/rust/sdk/crates/libs/sdk-enclave/src/etw.rs @@ -0,0 +1,350 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::common; +use alloc::{collections::BTreeMap, vec::Vec}; +use core::marker::PhantomData; +use sdk_enclave_gen::AbiError; +use sdk_enclave_gen::implementation::types as edl_types; +use sdk_enclave_gen::stubs::untrusted::{self}; +use spin::{Once, RwLock}; +use tracelogging::{Channel, Level, Opcode, Provider}; + +/// Information about a registered ETW provider. +/// The registration_id is generated during registration +/// and is used to look up the provider info during +/// the enablement callback from the etw framework. +struct EtwProviderInfo { + provider_id: uuid::Uuid, + registration_id: uuid::Uuid, + callback: ProviderEnableCallback, + callback_context: usize, +} + +#[derive(Default)] +struct EtwRegistrationMap { + table: BTreeMap, +} + +fn etw_registration_map() -> &'static RwLock { + static ETW_REGISTRATION_MAP: Once> = Once::new(); + ETW_REGISTRATION_MAP.call_once(|| RwLock::new(EtwRegistrationMap::default())) +} + +fn etw_providers() -> &'static RwLock> { + static ETW_PROVIDERS: Once>> = Once::new(); + ETW_PROVIDERS.call_once(|| RwLock::new(Vec::new())) +} + +pub fn register_providers() { + static REGISTER_PROVIDERS_ONCE: Once<()> = Once::new(); + REGISTER_PROVIDERS_ONCE.call_once(|| { + for &provider in etw_providers().read().iter() { + unsafe { + provider.register(); + } + } + }); +} + +/// Adds a single ETW provider to be registered later. +/// The provider must have been create using the `tracelogging` crate. +pub fn add_provider(provider: &'static Provider) { + etw_providers().write().push(provider); +} + +/// Adds multiple ETW providers to be registered later. +/// The provider must have been create using the `tracelogging` crate. +pub fn add_providers(providers: &[&'static Provider]) { + let mut etw_providers = etw_providers().write(); + for &provider in providers { + etw_providers.push(provider); + } +} + +/// Unregisters all ETW providers added via `add_provider` or `add_providers`. +pub fn unregister_providers() { + for provider in etw_providers().read().iter() { + provider.unregister(); + } +} + +#[repr(C)] +#[derive(Debug, Default)] +struct EventDataDescriptor<'a> { + ptr: u64, + size: u32, + reserved: u32, + lifetime: PhantomData<&'a [u8]>, +} + +#[repr(C)] +#[derive(Debug, Default)] +struct EventFilterDescriptor<'a> { + ptr: u64, + size: u32, + Type: u32, + lifetime: PhantomData<&'a [u8]>, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +struct EventDescriptor { + id: u16, + version: u8, + channel: Channel, + level: Level, + opcode: Opcode, + task: u16, + keyword: u64, +} + +fn uuid_to_abi(uuid: &uuid::Uuid) -> edl_types::Guid { + let uuid_fields = uuid.as_fields(); + edl_types::Guid { + data1: uuid_fields.0, + data2: uuid_fields.1, + data3: uuid_fields.2, + data4: *uuid_fields.3, + } +} + +#[inline] +fn result_from_abi(result: Result) -> u32 { + if let Ok(code) = result { + code + } else { + match result.err().unwrap() { + AbiError::Win32Error(code) => code, + // Can't convert HRESULT to Win32 error code directly, + // return a generic error code. + AbiError::Hresult(_) => 0x0000054F_u32, //ERROR_INTERNAL_ERROR + } + } +} + +/// Implementation of declared ETW Unregister function called by the ETW framework. +/// See: https://github.com/microsoft/tracelogging/blob/rust1.2.4/etw/rust/tracelogging/src/native.rs#L468 +#[unsafe(no_mangle)] +unsafe extern "system" fn EtwUnregister(reg_handle: u64) -> u32 { + let result = untrusted::event_unregister(reg_handle); + result_from_abi(result) +} + +type ProviderEnableCallback = fn( + source_id: &edl_types::Guid, + event_control_code: u32, + level: Level, + match_any_keyword: u64, + match_all_keyword: u64, + filter_data: usize, + callback_context: usize, +); + +/// Implementation of declared ETW Register function called by the ETW framework. +/// See: https://github.com/microsoft/tracelogging/blob/rust1.2.4/etw/rust/tracelogging/src/native.rs#L469 +#[allow(improper_ctypes_definitions)] // Callback is invoked in rust not C/C++ +#[unsafe(no_mangle)] +unsafe extern "system" fn EtwRegister( + provider_id: &edl_types::Guid, + outer_callback: ProviderEnableCallback, + outer_context: usize, + reg_handle: &mut u64, +) -> u32 { + // Use uuid crate to avoid using tracelogging::Guid::new() for the registration id. + // That API uses UuidCreate() from RPCRT4.dll which is not available to enclaves. + let registration_id = uuid::Uuid::new_v4(); + let uuid_prov_id = uuid::Uuid::from_fields( + provider_id.data1, + provider_id.data2, + provider_id.data3, + &provider_id.data4, + ); + + { + let info = EtwProviderInfo { + provider_id: uuid_prov_id, + registration_id, + callback: outer_callback, + callback_context: outer_context, + }; + let mut map = etw_registration_map().write(); + map.table.insert(registration_id, info); + } + + let enclave_info = match common::get_enclave_information() { + Ok(info) => info, + Err(_) => { + // There is no accurate NTSTATUS mapping for HRESULT errors, + // so return a generic error code. + return 0x0000054F_u32; //ERROR_INTERNAL_ERROR + } + }; + + let abi_result = untrusted::event_register( + &uuid_to_abi(&uuid_prov_id), + &uuid_to_abi(®istration_id), + enclave_info.BaseAddress as u64, // So the host can identify the enclave + reg_handle, + ); + + let result = result_from_abi(abi_result); + if result != 0 { + let mut map = etw_registration_map().write(); + map.table.remove(®istration_id); + } + + result +} + +/// Implementation of declared ETW SetInformation function called by the ETW framework. +/// See: https://github.com/microsoft/tracelogging/blob/rust1.2.4/etw/rust/tracelogging/src/native.rs#L475 +#[unsafe(no_mangle)] +unsafe extern "system" fn EtwSetInformation( + reg_handle: u64, + information_class: u32, + information: *const u8, + information_length: u32, +) -> u32 { + // Convert the raw pointer and length to a slice. This comes from the tracelogging + // crate itself, so we assume it's valid. We're just forwarding it through the abi + // layer. + // See: https://github.com/microsoft/tracelogging/blob/rust1.2.4/etw/rust/tracelogging/src/provider.rs#L248 + // See: https://github.com/microsoft/tracelogging/blob/rust1.2.4/etw/rust/tracelogging/src/provider.rs#L34 + // The meta variable is a static slice that describes the event data. + let info_slice = + unsafe { core::slice::from_raw_parts(information, information_length as usize) }; + + let result = + untrusted::event_set_information(reg_handle, information_class, &info_slice.to_vec()); + + result_from_abi(result) +} + +/// Implementation of declared ETW WriteTransfer function called by the ETW framework. +/// See: https://github.com/microsoft/tracelogging/blob/rust1.2.4/etw/rust/tracelogging/src/native.rs#L481 +#[unsafe(no_mangle)] +unsafe extern "system" fn EtwWriteTransfer( + reg_handle: u64, + descriptor: *const core::ffi::c_void, + activity_id: Option<&[u8; 16]>, + related_id: Option<&[u8; 16]>, + data_count: u32, + data: *const EventDataDescriptor, +) -> u32 { + let activity_guid = if let Some(activity_id) = activity_id { + let guid = uuid::Uuid::from_bytes_le(*activity_id); + Some(uuid_to_abi(&guid)) + } else { + None + }; + + let related_guid = if let Some(related_id) = related_id { + let guid = uuid::Uuid::from_bytes_le(*related_id); + Some(uuid_to_abi(&guid)) + } else { + None + }; + + // Value comes directly from tracelogging crate to pass to the real win32 function. + // So, it should be valid. We're just acting in the middle so we can pass it through + // the abi layer to the host. We confirmed the layout of their EventDescriptor + // matches ours since they both use repr(C). + let descriptor = unsafe { &*(descriptor as *const EventDescriptor) }; + + let abi_descriptor = edl_types::EventDescriptor { + id: descriptor.id, + version: descriptor.version, + channel: descriptor.channel.as_int(), + level: descriptor.level.as_int(), + opcode: descriptor.opcode.as_int(), + task: descriptor.task, + keyword: descriptor.keyword, + }; + + // Value comes directly from tracelogging crate to pass to the real win32 function. + // So, it should be valid. We're just acting in the middle so we can pass it through + // the abi layer and to the host. + let data_descriptor = unsafe { core::slice::from_raw_parts(data, data_count as usize) }; + + let mut abi_data: Vec = Vec::with_capacity(data_count as usize); + + for desc in data_descriptor.iter() { + // data comes from tracelogging crate, so it should be valid. + // See: https://github.com/microsoft/tracelogging/blob/rust1.2.4/etw/rust/tracelogging/src/descriptors.rs#L120 + let data_slice = + unsafe { core::slice::from_raw_parts(desc.ptr as *const u8, desc.size as usize) }; + + abi_data.push(edl_types::EventDataDescriptor { + descriptor: data_slice.to_vec(), + reserved: desc.reserved, + }); + } + + let result = untrusted::event_write_transfer( + reg_handle, + &abi_descriptor, + &activity_guid, + &related_guid, + &abi_data.to_vec(), + ); + + result_from_abi(result) +} + +/// Implementation of declared ETW ActivityIdControl function called by the ETW framework. +/// See: https://github.com/microsoft/tracelogging/blob/rust1.2.4/etw/rust/tracelogging/src/native.rs#L489 +#[unsafe(no_mangle)] +unsafe extern "system" fn EtwActivityIdControl( + control_code: u32, + activity_id: &mut edl_types::Guid, +) -> u32 { + let result = untrusted::event_activity_id_control(control_code, activity_id); + result_from_abi(result) +} + +pub fn etw_callback_passthrough( + registration_id: &edl_types::Guid, + source_id: &edl_types::Guid, + event_control_code: u32, + level: u8, + match_any_keyword: u64, + match_all_keyword: u64, + filter_data: &edl_types::EventFilterDescriptor, +) -> Result<(), AbiError> { + let reg_id = uuid::Uuid::from_fields( + registration_id.data1, + registration_id.data2, + registration_id.data3, + ®istration_id.data4, + ); + + let map = etw_registration_map().read(); + let info: &EtwProviderInfo = map + .table + .get(®_id) + .ok_or(AbiError::Win32Error(0x00000490))?; // ERROR_NOT_FOUND + + let filter_data = EventFilterDescriptor { + ptr: filter_data.descriptor.as_ptr() as u64, + size: filter_data.descriptor.len() as u32, + Type: filter_data.type_data, + lifetime: PhantomData, + }; + + // Call the etw frameworks original callback function that was passed during registration. + // this will then call the developers callback they passed to provider.register_with_callback(). + // See: https://github.com/microsoft/tracelogging/blob/rust1.2.4/etw/rust/tracelogging/src/provider.rs#L217 + // The etw frameworks callback is guaranteed to be valid since it was passed during registration. + (info.callback)( + source_id, + event_control_code, + level.into(), + match_any_keyword, + match_all_keyword, + &filter_data as *const EventFilterDescriptor as usize, + info.callback_context, + ); + + Ok(()) +} diff --git a/rust/sdk/crates/libs/sdk-enclave/src/lib.rs b/rust/sdk/crates/libs/sdk-enclave/src/lib.rs index a0a35327..e8049e4d 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/lib.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/lib.rs @@ -2,16 +2,18 @@ // Licensed under the MIT License. #![no_std] +#![allow(dead_code)] +#![allow(non_snake_case)] extern crate alloc; -mod enclave_ffi; - pub mod common; +pub mod etw; pub mod userboundkey; -// Re-export the SDK generated enclave crate. -// This is used internally by the export_sdk_enclave_functions! macro. -pub use sdk_enclave_gen; +// Re-export generated SDK enclave's export macro +// and println stub for use in this crate's macros. +pub use sdk_enclave_gen::export_enclave_functions; +pub use sdk_enclave_gen::stubs::untrusted::println; /// Export all SDK enclave functions. /// @@ -34,9 +36,22 @@ macro_rules! export_sdk_enclave_functions { // Export SDK enclave functions in a private module // to avoid naming conflicts with the application's own exports mod __sdk_exports { - $crate::sdk_enclave_gen::export_enclave_functions!($crate::common::EnclaveImpl); + $crate::export_enclave_functions!($crate::common::EnclaveImpl); } - - // Future SDK features will be added here as additional modules }; } + +/// Print formatted output from the enclave to the host's standard output. +/// This macro works like `format!` and appends a newline. +/// # Example +/// ```rust,ignore +/// use vbsenclave_sdk_enclave::enclave_println; +/// enclave_println!("Hello from the enclave: {}", 42); +/// ``` +#[macro_export] +macro_rules! enclave_println { + ($($arg:tt)*) => {{ + let mut s = alloc::format!($($arg)*); + let _ = $crate::println(&s); + }}; +} diff --git a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/types.rs b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/types.rs index 6be9cfa7..7ae8b9d1 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/types.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/types.rs @@ -3,10 +3,9 @@ //! Type definitions for user-bound key operations -use crate::common::{CryptoError, EnclaveUtilsError}; +use crate::common::{CryptoError, EnclaveUtilsError, ntstatus_to_hresult}; use alloc::string::String; use sdk_enclave_gen::AbiError; -use windows_enclave::vertdll::RtlNtStatusToDosError; /// Error type for user-bound key operations #[derive(Debug)] @@ -60,29 +59,12 @@ impl From for UserBoundKeyError { } } -/// Converts a Win32 error code to an HRESULT. -/// Equivalent to the HRESULT_FROM_WIN32 macro. -#[inline] -fn hresult_from_win32(error: u32) -> i32 { - if error == 0 { - 0 // S_OK - } else { - // FACILITY_WIN32 = 7, so (7 << 16) | 0x80000000 = 0x80070000 - ((error & 0x0000FFFF) | 0x80070000) as i32 - } -} - impl UserBoundKeyError { /// Convert the error to an HRESULT code pub fn to_hresult(&self) -> i32 { match self { UserBoundKeyError::Hresult(hr) => *hr, - // NTSTATUS to HRESULT: Convert via RtlNtStatusToDosError then HRESULT_FROM_WIN32 - UserBoundKeyError::NtStatus(status) => { - // SAFETY: RtlNtStatusToDosError is a pure function that converts NTSTATUS to Win32 error - let win32_error = unsafe { RtlNtStatusToDosError(*status) }; - hresult_from_win32(win32_error) - } + UserBoundKeyError::NtStatus(status) => ntstatus_to_hresult(*status), UserBoundKeyError::AbiError(e) => e.to_hresult().0, UserBoundKeyError::AllocationFailed => -2147024882, // E_OUTOFMEMORY UserBoundKeyError::InvalidData(_) => -2147024809, // E_INVALIDARG diff --git a/rust/sdk/crates/libs/sdk-host/Cargo.toml b/rust/sdk/crates/libs/sdk-host/Cargo.toml index 4a9ed7d2..e4861cfe 100644 --- a/rust/sdk/crates/libs/sdk-host/Cargo.toml +++ b/rust/sdk/crates/libs/sdk-host/Cargo.toml @@ -8,6 +8,7 @@ workspace = true features = [ "Win32_System", "Win32_System_Memory", + "Win32_System_Diagnostics_Etw", "Win32_System_Threading", "Win32_Security", "Win32_Security_Authorization", @@ -20,6 +21,7 @@ features = [ sdk_host_gen = { path = "./generated/sdk_gen" } spin = { workspace = true } userboundkey-kcm = { workspace = true } +uuid = {version = "1.19.0", default-features = false } widestring = { workspace = true } windows-core = { workspace = true } windows-future = { workspace = true } \ No newline at end of file diff --git a/rust/sdk/crates/libs/sdk-host/src/common/mod.rs b/rust/sdk/crates/libs/sdk-host/src/common/mod.rs index 1e4cd4e5..f92e0ee4 100644 --- a/rust/sdk/crates/libs/sdk-host/src/common/mod.rs +++ b/rust/sdk/crates/libs/sdk-host/src/common/mod.rs @@ -6,3 +6,8 @@ pub mod sdk_impls; pub use sdk_impls::HostImpl; + +/// Print a message from the enclave to the host's standard output. +pub fn enclave_println(msg: &String) { + println!("[Enclave] {}", msg); +} diff --git a/rust/sdk/crates/libs/sdk-host/src/common/sdk_impls.rs b/rust/sdk/crates/libs/sdk-host/src/common/sdk_impls.rs index bdf108ac..355650fe 100644 --- a/rust/sdk/crates/libs/sdk-host/src/common/sdk_impls.rs +++ b/rust/sdk/crates/libs/sdk-host/src/common/sdk_impls.rs @@ -10,10 +10,12 @@ use sdk_host_gen::AbiError; use sdk_host_gen::implementation::types::{ - credentialAndSessionInfo, edl::WString, keyCredentialCacheConfig, + EventDataDescriptor, EventDescriptor, Guid, credentialAndSessionInfo, edl::WString, + keyCredentialCacheConfig, }; use sdk_host_gen::implementation::untrusted::Untrusted; +use crate::etw; use crate::userboundkey; /// SDK host implementation of the Untrusted trait. @@ -88,4 +90,47 @@ impl Untrusted for HostImpl { fn userboundkey_delete_credential(credential: u64) -> Result<(), AbiError> { userboundkey::userboundkey_delete_credential(credential) } + + fn event_unregister(reg_handle: u64) -> Result { + etw::event_unregister(reg_handle) + } + + fn event_register( + provider_id: &Guid, + registration_id: &Guid, + enclave: u64, + reg_handle: &mut u64, + ) -> Result { + etw::event_register(provider_id, registration_id, enclave, reg_handle) + } + + fn event_write_transfer( + reg_handle: u64, + descriptor: &EventDescriptor, + activity_id: &Option, + related_id: &Option, + user_data: &Vec, + ) -> Result { + etw::event_write_transfer(reg_handle, descriptor, activity_id, related_id, user_data) + } + + fn event_set_information( + reg_handle: u64, + information_class: u32, + information: &Vec, + ) -> Result { + etw::event_set_information(reg_handle, information_class, information) + } + + fn event_activity_id_control( + control_code: u32, + activity_id: &mut Guid, + ) -> Result { + etw::event_activity_id_control(control_code, activity_id) + } + + fn println(msg: &String) -> Result<(), AbiError> { + super::enclave_println(msg); + Ok(()) + } } diff --git a/rust/sdk/crates/libs/sdk-host/src/enclave.rs b/rust/sdk/crates/libs/sdk-host/src/enclave.rs index 181dfd37..4a6b8413 100644 --- a/rust/sdk/crates/libs/sdk-host/src/enclave.rs +++ b/rust/sdk/crates/libs/sdk-host/src/enclave.rs @@ -9,13 +9,16 @@ //! # Example //! //! ```no_run -//! use vbsenclave_sdk_host::enclave::{EnclaveHandle, megabytes}; +//! use vbsenclave_sdk_host::enclave::{ +//! EnclaveHandle, megabytes, ENCLAVE_CREATE_INFO_FLAG_RELEASE +//! }; //! //! // Create and initialize an enclave //! let enclave = EnclaveHandle::create_and_initialize( //! "my_enclave.dll", //! megabytes(32), //! None, // Use default owner ID +//! ENCLAVE_CREATE_INFO_FLAG_RELEASE, //! )?; //! //! // Use enclave.as_ptr() for enclave calls @@ -107,6 +110,17 @@ impl Default for EnclaveInitInfoVbs { } } +const DEFAULT_ENCLAVE_THREADS: u32 = 2; + +/// Enclave creation flag to enable debuggability of the +/// vbs enclave. Do not use this flag for production enclaves. +pub const ENCLAVE_CREATE_INFO_FLAG_DEBUG: u32 = 0x1; + +/// Enclave creation flag to disable debuggability of the +/// vbs enclave. +/// Use this for production enclaves. +pub const ENCLAVE_CREATE_INFO_FLAG_RELEASE: u32 = 0x0; + /// Convert megabytes to bytes. #[inline] pub const fn megabytes(mb: usize) -> usize { @@ -327,12 +341,15 @@ impl EnclaveHandle { /// # Example /// /// ```no_run - /// use vbsenclave_sdk_host::enclave::{EnclaveHandle, megabytes}; + /// use vbsenclave_sdk_host::enclave::{ + /// EnclaveHandle, ENCLAVE_CREATE_INFO_FLAG_RELEASE, megabytes + /// }; /// /// let enclave = EnclaveHandle::create_and_initialize( /// "my_enclave.dll", /// megabytes(32), /// None, + /// ENCLAVE_CREATE_INFO_FLAG_RELEASE, /// )?; /// # Ok::<(), windows::core::Error>(()) /// ``` @@ -340,9 +357,16 @@ impl EnclaveHandle { dll_path: &str, size: usize, owner_id: Option<&[u8]>, + flags: u32, ) -> Result { // Default to 2 threads for callback support (e.g., Windows Hello attestation) - Self::create_and_initialize_with_threads(dll_path, size, owner_id, 2) + Self::create_and_initialize_with_threads( + dll_path, + size, + owner_id, + flags, + DEFAULT_ENCLAVE_THREADS, + ) } /// Create, load, and initialize a VBS enclave with a specific thread count. @@ -360,10 +384,11 @@ impl EnclaveHandle { dll_path: &str, size: usize, owner_id: Option<&[u8]>, + flags: u32, thread_count: u32, ) -> Result { // Create the enclave - let enclave_ptr = create(size, owner_id, 0, 0)?; + let enclave_ptr = create(size, owner_id, flags, 0)?; // If any subsequent step fails, we need to clean up let handle = unsafe { Self::from_raw(enclave_ptr) }; @@ -398,6 +423,9 @@ impl EnclaveHandle { impl Drop for EnclaveHandle { fn drop(&mut self) { if !self.enclave.is_null() { + let host = sdk_host_gen::SdkHost::new(self.enclave); + let _ = host.unregister_etw_providers(); + // SAFETY: self.enclave is valid because it came from create() // and we only set it to null after this point unsafe { diff --git a/rust/sdk/crates/libs/sdk-host/src/etw.rs b/rust/sdk/crates/libs/sdk-host/src/etw.rs new file mode 100644 index 00000000..594cd9a9 --- /dev/null +++ b/rust/sdk/crates/libs/sdk-host/src/etw.rs @@ -0,0 +1,236 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use sdk_host_gen::AbiError; +use sdk_host_gen::SdkHost; +use sdk_host_gen::implementation::types as codegen_types; +use std::sync::{OnceLock, RwLock}; +use windows::Win32::System::Diagnostics::Etw; + +#[repr(C)] +pub struct EtwProviderContext { + provider_id: codegen_types::Guid, + registration_id: codegen_types::Guid, + enclave: u64, +} + +#[derive(Default)] +pub struct EtwContextList { + #[allow(clippy::vec_box)] + /// Each entry is boxed to guarantee a stable address for ETW callbacks. + list: Vec>, +} + +static ETW_CONTEXT_LIST: OnceLock> = OnceLock::new(); + +pub fn etw_context_list() -> &'static RwLock { + ETW_CONTEXT_LIST.get_or_init(|| RwLock::new(EtwContextList::default())) +} + +fn winguid_to_abi_guid(guid: &windows::core::GUID) -> codegen_types::Guid { + codegen_types::Guid { + data1: guid.data1, + data2: guid.data2, + data3: guid.data3, + data4: guid.data4, + } +} + +fn abi_guid_to_winguid(guid: &codegen_types::Guid) -> windows::core::GUID { + windows::core::GUID::from_values(guid.data1, guid.data2, guid.data3, guid.data4) +} + +fn opt_abi_guid_to_opt_winguid(guid: &Option) -> Option { + guid.as_ref() + .map(|g| windows::core::GUID::from_values(g.data1, g.data2, g.data3, g.data4)) +} + +// This is the callback function that will be called by the ETW framework when +// an enablement/disablement event is triggered. We will forward the contents +// of this callback into the enclave and invoke the users original callback function +// there. +#[unsafe(no_mangle)] +unsafe extern "system" fn etw_call_back( + source_id: *const windows::core::GUID, + is_enabled: Etw::ENABLECALLBACK_ENABLED_STATE, + level: u8, + match_any_keyword: u64, + match_all_keyword: u64, + filter_data: *const Etw::EVENT_FILTER_DESCRIPTOR, + callback_context: *mut core::ffi::c_void, +) { + if source_id.is_null() { + // This comes from the etw framework. It should never happen. If it does, we cannot proceed. + // Etw is best-effort delivery, so we will just return. + return; + } + + if callback_context.is_null() { + // This comes from the etw framework. It should never happen. If it does, we cannot proceed. + // Etw is best-effort delivery, so we will just return. + return; + } + + // The callback from the Etw framework does not manipulate the context that we originally passed in. + // It simply passes it back to us. So, we can safely cast it back to our EtwProviderContext object. + // We have to use unsafe because we're dereferencing a raw pointer in rust. + let context = unsafe { &*(callback_context as *const EtwProviderContext) }; + + // At this point edlcodegen callback infrastructure is already registered. So, we can create + // the host interface to call the etw passthrough callback. + let host = SdkHost::new(context.enclave as *mut core::ffi::c_void); + + // Windows guid pointer comes from the Etw Framework. It is expected to be valid. We have to use unsafe because + // we're dereferencing a raw pointer in rust. + let win_guid = unsafe { &*(source_id) }; + let abi_source_guid = winguid_to_abi_guid(win_guid); + + let mut abi_filter_descriptor = codegen_types::EventFilterDescriptor { + descriptor: Vec::new(), + type_data: 0, + }; + + if !filter_data.is_null() { + // Windows filter data pointer comes from the Etw Framework. It is expected to be valid. + // We have to use unsafe because we're dereferencing a raw pointer in rust. + let win_filter_data = unsafe { &*(filter_data) }; + let filter_slice = unsafe { + std::slice::from_raw_parts( + win_filter_data.Ptr as *const u8, + win_filter_data.Size as usize, + ) + }; + + abi_filter_descriptor.descriptor = filter_slice.to_vec(); + abi_filter_descriptor.type_data = win_filter_data.Type; + } + + // send callback data back to enclave + let _ = host.etw_callback_passthrough( + &context.registration_id, + &abi_source_guid, + is_enabled.0, + level, + match_any_keyword, + match_all_keyword, + &abi_filter_descriptor, + ); +} + +pub fn event_unregister(reg_handle: u64) -> Result { + let result = unsafe { Etw::EventUnregister(Etw::REGHANDLE(reg_handle as i64)) }; + Ok(result) +} + +pub fn event_register( + provider_id: &codegen_types::Guid, + registration_id: &codegen_types::Guid, + enclave: u64, + reg_handle: &mut u64, +) -> Result { + let guid = abi_guid_to_winguid(provider_id); + let mut etw_context = etw_context_list() + .write() + .expect("Failed to acquire ETW context list write lock"); + + let boxed_context = Box::new(EtwProviderContext { + provider_id: provider_id.clone(), + registration_id: registration_id.clone(), + enclave, + }); + + let context_ptr = Box::as_ref(&boxed_context) as *const _ as *const core::ffi::c_void; + etw_context.list.push(boxed_context); + + let result = unsafe { + let mut handle = Etw::REGHANDLE(0); + let res_code = + Etw::EventRegister(&guid, Some(etw_call_back), Some(context_ptr), &mut handle); + + *reg_handle = handle.0 as u64; + res_code + }; + + Ok(result) +} + +pub fn event_write_transfer( + reg_handle: u64, + descriptor: &codegen_types::EventDescriptor, + activity_id: &Option, + related_id: &Option, + user_data: &[codegen_types::EventDataDescriptor], +) -> Result { + let etw_descriptor = Etw::EVENT_DESCRIPTOR { + Id: descriptor.id, + Version: descriptor.version, + Channel: descriptor.channel, + Level: descriptor.level, + Opcode: descriptor.opcode, + Task: descriptor.task, + Keyword: descriptor.keyword, + }; + + let mut descriptors: Vec = Vec::with_capacity(user_data.len()); + for desc in user_data.iter() { + descriptors.push(Etw::EVENT_DATA_DESCRIPTOR { + Ptr: desc.descriptor.as_ptr() as u64, + Size: desc.descriptor.len() as u32, + Anonymous: Etw::EVENT_DATA_DESCRIPTOR_0 { + Reserved: desc.reserved, + }, + }); + } + + let mut op_descriptor: Option<&[Etw::EVENT_DATA_DESCRIPTOR]> = None; + if !descriptors.is_empty() { + op_descriptor = Some(descriptors.as_slice()); + } + + let win_activity_id = opt_abi_guid_to_opt_winguid(activity_id); + let win_activity_id_ptr = win_activity_id.as_ref().map(|g| g as *const _); + + let win_related_id = opt_abi_guid_to_opt_winguid(related_id); + let win_related_id_ptr = win_related_id.as_ref().map(|g| g as *const _); + + let result = unsafe { + Etw::EventWriteTransfer( + Etw::REGHANDLE(reg_handle as i64), + &etw_descriptor, + win_activity_id_ptr, + win_related_id_ptr, + op_descriptor, + ) + }; + + Ok(result) +} + +pub fn event_set_information( + reg_handle: u64, + information_class: u32, + information: &[u8], +) -> Result { + let info_class = Etw::EVENT_INFO_CLASS(information_class as i32); + let result = unsafe { + Etw::EventSetInformation( + Etw::REGHANDLE(reg_handle as i64), + info_class, + information.as_ptr() as *const core::ffi::c_void, + information.len() as u32, + ) + }; + + Ok(result) +} + +pub fn event_activity_id_control( + control_code: u32, + activity_id: &mut codegen_types::Guid, +) -> Result { + let mut win_guid = abi_guid_to_winguid(activity_id); + let result = unsafe { Etw::EventActivityIdControl(control_code, &mut win_guid) }; + + *activity_id = winguid_to_abi_guid(&win_guid); + Ok(result) +} diff --git a/rust/sdk/crates/libs/sdk-host/src/host_ffi.rs b/rust/sdk/crates/libs/sdk-host/src/host_ffi.rs deleted file mode 100644 index fc36ab24..00000000 --- a/rust/sdk/crates/libs/sdk-host/src/host_ffi.rs +++ /dev/null @@ -1,2 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. diff --git a/rust/sdk/crates/libs/sdk-host/src/lib.rs b/rust/sdk/crates/libs/sdk-host/src/lib.rs index 62760fbf..3351401a 100644 --- a/rust/sdk/crates/libs/sdk-host/src/lib.rs +++ b/rust/sdk/crates/libs/sdk-host/src/lib.rs @@ -1,17 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -mod host_ffi; - +#![allow(dead_code)] pub mod common; pub mod enclave; +mod etw; pub mod userboundkey; -// Re-export AbiError for consumers who call register_sdk_callbacks -pub use sdk_host_gen::AbiError; - -// Re-export HostImpl for consumers -pub use common::HostImpl; +use common::HostImpl; +use sdk_host_gen::AbiError; // The userboundkey-kcm crate is not intended to be published as a standalone package. // Long term, these KCM APIs should be consumed from the windows-rs crate. @@ -24,7 +21,8 @@ pub use userboundkey_kcm::KeyCredentialManager; /// /// Call this function once after loading your enclave to enable SDK features. /// This registers the necessary callbacks that allow the enclave's SDK functions -/// to communicate back to VTL0 (e.g., for Windows Hello integration). +/// to communicate back to VTL0 (e.g., for Windows Hello integration). If you added +/// ETW providers via the enclave SDK, this function also registers those providers. /// /// # Arguments /// @@ -44,11 +42,9 @@ pub use userboundkey_kcm::KeyCredentialManager; /// ``` pub fn register_sdk_callbacks(enclave_ptr: *mut core::ffi::c_void) -> Result<(), AbiError> { // Register SDK callbacks using the SdkHost interface and HostImpl - let sdk_wrapper = userboundkey::SdkHost::new(enclave_ptr); + let sdk_wrapper = sdk_host_gen::SdkHost::new(enclave_ptr); sdk_wrapper.register_vtl0_callbacks::()?; - - // Future SDK features will be added here: - // #[cfg(feature = "secure_storage")] + sdk_wrapper.register_etw_providers()?; Ok(()) } diff --git a/rust/sdk/crates/samples/userboundkey/enclave/src/lib.rs b/rust/sdk/crates/samples/userboundkey/enclave/src/lib.rs index 4bd971e8..131a0337 100644 --- a/rust/sdk/crates/samples/userboundkey/enclave/src/lib.rs +++ b/rust/sdk/crates/samples/userboundkey/enclave/src/lib.rs @@ -12,6 +12,8 @@ extern crate alloc; mod edl_impls; use core::panic::PanicInfo; + +#[allow(unused_imports)] use windows_enclave::vertdll::{ IMAGE_ENCLAVE_FLAG_PRIMARY_IMAGE, IMAGE_ENCLAVE_MINIMUM_CONFIG_SIZE, IMAGE_ENCLAVE_POLICY_DEBUGGABLE, ImageEnclaveConfig, diff --git a/rust/sdk/crates/samples/userboundkey/host/src/main.rs b/rust/sdk/crates/samples/userboundkey/host/src/main.rs index 8658b43b..2e54e3bb 100644 --- a/rust/sdk/crates/samples/userboundkey/host/src/main.rs +++ b/rust/sdk/crates/samples/userboundkey/host/src/main.rs @@ -89,6 +89,18 @@ fn get_secure_id_from_windows_hello() -> Result, Box