From d0739be46b50930d65f117a9bd10dba86d37e8b4 Mon Sep 17 00:00:00 2001 From: Branden Bonaby <105318831+bbonaby@users.noreply.github.com> Date: Tue, 20 Jan 2026 21:12:11 -0800 Subject: [PATCH 1/5] add initial etw code and readme --- .../crates/samples/helloworld/Cargo.toml | 3 + .../samples/helloworld/enclave/Cargo.toml | 2 + .../helloworld/enclave/src/edl_impls.rs | 9 + .../samples/helloworld/enclave/src/lib.rs | 19 +- .../crates/samples/helloworld/host/Cargo.toml | 2 + .../samples/helloworld/host/src/container.rs | 3 + .../samples/helloworld/host/src/main.rs | 9 +- rust/scripts/generate_codegen_crates.ps1 | 20 +- rust/sdk/Cargo.toml | 2 + rust/sdk/crates/etw.md | 80 ++++ rust/sdk/crates/libs/edl/veil_abi.edl | 15 + .../sdk/crates/libs/edl/veil_tracelogging.edl | 78 ++++ rust/sdk/crates/libs/sdk-enclave/Cargo.toml | 4 +- rust/sdk/crates/libs/sdk-enclave/src/etw.rs | 368 ++++++++++++++++++ rust/sdk/crates/libs/sdk-enclave/src/lib.rs | 15 + rust/sdk/crates/libs/sdk-host/Cargo.toml | 2 +- rust/sdk/crates/libs/sdk-host/src/etw.rs | 249 ++++++++++++ rust/sdk/crates/libs/sdk-host/src/lib.rs | 28 ++ rust/sdk/generate_codegen_for_workspace.ps1 | 6 +- .../code_gen_test_gen/src/abi/abi_types.rs | 2 + .../src/implementation/types.rs | 2 + .../code_gen_test_gen/src/abi/abi_types.rs | 2 + .../src/implementation/types.rs | 2 + .../Includes/CodeGeneration/Rust/Constants.h | 1 + 24 files changed, 911 insertions(+), 12 deletions(-) create mode 100644 rust/sdk/crates/etw.md create mode 100644 rust/sdk/crates/libs/edl/veil_abi.edl create mode 100644 rust/sdk/crates/libs/edl/veil_tracelogging.edl create mode 100644 rust/sdk/crates/libs/sdk-enclave/src/etw.rs create mode 100644 rust/sdk/crates/libs/sdk-host/src/etw.rs diff --git a/rust/edlcodegen/crates/samples/helloworld/Cargo.toml b/rust/edlcodegen/crates/samples/helloworld/Cargo.toml index cf3238f9..13ce1710 100644 --- a/rust/edlcodegen/crates/samples/helloworld/Cargo.toml +++ b/rust/edlcodegen/crates/samples/helloworld/Cargo.toml @@ -16,3 +16,6 @@ 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" } diff --git a/rust/edlcodegen/crates/samples/helloworld/enclave/Cargo.toml b/rust/edlcodegen/crates/samples/helloworld/enclave/Cargo.toml index 0680e98e..8a176a16 100644 --- a/rust/edlcodegen/crates/samples/helloworld/enclave/Cargo.toml +++ b/rust/edlcodegen/crates/samples/helloworld/enclave/Cargo.toml @@ -13,3 +13,5 @@ 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 } \ 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/lib.rs b/rust/edlcodegen/crates/samples/helloworld/enclave/src/lib.rs index ce602b82..0e4492b2 100644 --- a/rust/edlcodegen/crates/samples/helloworld/enclave/src/lib.rs +++ b/rust/edlcodegen/crates/samples/helloworld/enclave/src/lib.rs @@ -6,7 +6,8 @@ extern crate alloc; mod edl_impls; mod enclave_constants; use enclave_constants::*; -use core::panic::PanicInfo; +use core::panic::{ PanicInfo}; +use tracelogging as tlg; // Developer creates their own panic handler for no_std environments. // Note: The rust analyzer in VSCode shows red squiggly lines in the IDE. @@ -55,11 +56,25 @@ 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 { + + // Export SDK enclave functions from dll. + vbsenclave_sdk_enclave::export_enclave_functions(); + + // 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 index 17af9620..c0f8cacf 100644 --- a/rust/edlcodegen/crates/samples/helloworld/host/src/container.rs +++ b/rust/edlcodegen/crates/samples/helloworld/host/src/container.rs @@ -103,6 +103,9 @@ impl EnclaveContainer { unsafe fn unload_enclave(enclave: *mut c_void) { if !enclave.is_null() { unsafe { + // Explicitly unregister ETW providers before unloading the enclave. + let _ = vbsenclave_sdk_host::unregister_etw_providers(enclave); + if !TerminateEnclave(enclave, TRUE).as_bool() { print_unload_error("Failed to terminate enclave before deletion", HRESULT::from_thread()); return; diff --git a/rust/edlcodegen/crates/samples/helloworld/host/src/main.rs b/rust/edlcodegen/crates/samples/helloworld/host/src/main.rs index 6d649753..ddf89467 100644 --- a/rust/edlcodegen/crates/samples/helloworld/host/src/main.rs +++ b/rust/edlcodegen/crates/samples/helloworld/host/src/main.rs @@ -15,13 +15,16 @@ fn main() -> Result<(), Box> { let test_host = TestVtl0Host::new(enclave_container.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()); + } + + if let Err(err) = vbsenclave_sdk_host::register_sdk_callbacks(enclave_container.enclave) { + 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 8ebc3682..123322f1 100644 --- a/rust/scripts/generate_codegen_crates.ps1 +++ b/rust/scripts/generate_codegen_crates.ps1 @@ -13,7 +13,9 @@ param( [Parameter(Mandatory = $true)] [string]$Namespace = "", - [string]$Vtl0ClassName = "" + [string]$Vtl0ClassName = "", + + [string]$ImportDirectories = "" ) $ErrorActionPreference = "Stop" @@ -27,7 +29,13 @@ $ErrorActionPreference = "Stop" --language rust ` --EdlPath $EdlPath ` --VirtualTrustLayer enclave ` - --OutputDirectory $EnclaveOutDir + --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 ` @@ -36,4 +44,10 @@ $ErrorActionPreference = "Stop" --EdlPath $EdlPath ` --VirtualTrustLayer hostapp ` --Vtl0ClassName $Vtl0ClassName ` - --OutputDirectory $HostAppOutDir + --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 953b7b76..6335f0d4 100644 --- a/rust/sdk/Cargo.toml +++ b/rust/sdk/Cargo.toml @@ -16,7 +16,9 @@ edition = "2024" [workspace.dependencies] 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" } windows = "0.62.2" diff --git a/rust/sdk/crates/etw.md b/rust/sdk/crates/etw.md new file mode 100644 index 00000000..afff7e16 --- /dev/null +++ b/rust/sdk/crates/etw.md @@ -0,0 +1,80 @@ +# 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 unregistration, and transparently forwards ETW calls between the +enclave and the host. No additional integration steps are required. + +## Enclave Usage + +### 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; +#[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 { + // Export SDK enclave functions + vbsenclave_sdk_enclave::export_enclave_functions(); + vbsenclave_sdk_enclave::etw::add_provider(&HELLO_WORLD_PROVIDER); + } + + true +} +``` + +## Host Usage + +### 1. Add dependencies + +```toml +[dependencies] +vbsenclave-sdk-host = "0.1.0" +``` + +### 2. Register SDK callbacks after enclave is loaded and initialized + +```rust +// Call at least once after enclave initialization. +vbsenclave_sdk_host::register_sdk_callbacks(enclave)?; +``` + +### 3. Unregister providers during teardown + +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. diff --git a/rust/sdk/crates/libs/edl/veil_abi.edl b/rust/sdk/crates/libs/edl/veil_abi.edl new file mode 100644 index 00000000..5a680828 --- /dev/null +++ b/rust/sdk/crates/libs/edl/veil_abi.edl @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +enclave +{ + import "veil_tracelogging.edl"; + + trusted + { + }; + + untrusted + { + }; +}; diff --git a/rust/sdk/crates/libs/edl/veil_tracelogging.edl b/rust/sdk/crates/libs/edl/veil_tracelogging.edl new file mode 100644 index 00000000..a9f89737 --- /dev/null +++ b/rust/sdk/crates/libs/edl/veil_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 d06cb53e..fe2adc96 100644 --- a/rust/sdk/crates/libs/sdk-enclave/Cargo.toml +++ b/rust/sdk/crates/libs/sdk-enclave/Cargo.toml @@ -5,6 +5,8 @@ edition = { workspace = true } [dependencies] spin = { workspace = true } + +tracelogging = { workspace = true, default-features = false, features = ["etw", "kernel_mode"] } +uuid = { workspace = true, default-features = false, features = [ "v4"] } veil_abi_enclave_gen = { path = "./generated/veil_abi_gen" } windows-enclave = {workspace = true } - 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..4b19a3a5 --- /dev/null +++ b/rust/sdk/crates/libs/sdk-enclave/src/etw.rs @@ -0,0 +1,368 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use alloc::{collections::BTreeMap, vec::Vec}; +use core::marker::PhantomData; +use core::mem; +use spin::{Once, RwLock}; +use tracelogging::{Channel, Level, Opcode, Provider}; +use veil_abi_enclave_gen::AbiError; +use veil_abi_enclave_gen::implementation::trusted::Trusted; +use veil_abi_enclave_gen::implementation::types as edl_types; +use veil_abi_enclave_gen::stubs::untrusted::{self}; +use windows_enclave::vertdll; + +/// 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())) +} + +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(); + } + } + }); +} + +/// Public function to be called by developer at dll load time. +/// 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); +} + +/// Public function to be called by developer at dll load time. +/// 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); + } +} + +/// Public function to be called by developer at dll unload time. +/// 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. + // TODO: improve this mapping if needed. + 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 mut enclave_info = vertdll::ENCLAVE_INFORMATION::default(); + + let hr = unsafe { + vertdll::EnclaveGetEnclaveInformation( + mem::size_of::() as u32, + &mut enclave_info, + ) + }; + + if hr != 0 { + return 0x0000054F_u32; // ERROR_INTERNAL_ERROR + } + + let 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, + ); + + result_from_abi(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 struct EnclaveImpl {} + +impl Trusted for EnclaveImpl { + 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(()) + } + + fn register_etw_providers() -> Result<(), AbiError> { + register_providers(); + Ok(()) + } + + fn unregister_etw_providers() -> Result<(), AbiError> { + unregister_providers(); + Ok(()) + } +} diff --git a/rust/sdk/crates/libs/sdk-enclave/src/lib.rs b/rust/sdk/crates/libs/sdk-enclave/src/lib.rs index 5aa5786a..62865210 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/lib.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/lib.rs @@ -2,6 +2,21 @@ // Licensed under the MIT License. #![no_std] +#![allow(dead_code)] +#![allow(non_snake_case)] + +use veil_abi_enclave_gen::export_enclave_functions; extern crate alloc; mod enclave_ffi; +pub mod etw; + +// Re-export Uuid from the uuid crate for use in enclave code. +pub use uuid::Uuid; + +/// Developer should call this function in their dllmain to export the +/// SDK enclave functions. +#[inline(always)] +pub fn export_enclave_functions() { + export_enclave_functions!(etw::EnclaveImpl); +} diff --git a/rust/sdk/crates/libs/sdk-host/Cargo.toml b/rust/sdk/crates/libs/sdk-host/Cargo.toml index 694f9fe7..a3f9a4ad 100644 --- a/rust/sdk/crates/libs/sdk-host/Cargo.toml +++ b/rust/sdk/crates/libs/sdk-host/Cargo.toml @@ -8,12 +8,12 @@ workspace = true features = [ "Win32_System", "Win32_System_Memory", + "Win32_System_Diagnostics_Etw", "Security_Credentials", "Security_Cryptography", "Security_Cryptography_Certificates" ] [dependencies] -spin = { workspace = true } userboundkey-kcm = { workspace = true } veil_abi_host_gen = { path = "./generated/veil_abi_gen" } \ No newline at end of file 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..61f10efb --- /dev/null +++ b/rust/sdk/crates/libs/sdk-host/src/etw.rs @@ -0,0 +1,249 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::sync::{OnceLock, RwLock}; +use veil_abi_host_gen::AbiError; +use veil_abi_host_gen::export_interface; +use veil_abi_host_gen::implementation::types as codegen_types; +use veil_abi_host_gen::implementation::untrusted::Untrusted; +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 { + 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) +} + +// 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 interface to call the etw passthrough callback. + let interface = export_interface::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 _ = interface.etw_callback_passthrough( + &context.registration_id, + &abi_source_guid, + is_enabled.0, + level, + match_any_keyword, + match_all_keyword, + &abi_filter_descriptor, + ); +} + +pub struct HostImpl {} + +impl Untrusted for HostImpl { + fn event_unregister(reg_handle: u64) -> Result { + let result = unsafe { Etw::EventUnregister(Etw::REGHANDLE(reg_handle as i64)) }; + Ok(result) + } + + 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"); + + etw_context.list.push(EtwProviderContext { + provider_id: provider_id.clone(), + registration_id: registration_id.clone(), + enclave, + }); + + let context_entry = etw_context.list.last().unwrap(); + let last_entry_pvoid = context_entry as *const _ as *const core::ffi::c_void; + + let result = unsafe { + let mut handle = Etw::REGHANDLE(0); + let res_code = Etw::EventRegister( + &guid, + Some(etw_call_back), + Some(last_entry_pvoid), + &mut handle, + ); + + *reg_handle = handle.0 as u64; + res_code + }; + + Ok(result) + } + + fn event_write_transfer( + reg_handle: u64, + descriptor: &codegen_types::EventDescriptor, + activity_id: &Option, + related_id: &Option, + user_data: &Vec, + ) -> 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 etw_activity_id = match activity_id { + Some(guid) => { + let win_guid = abi_guid_to_winguid(guid); + Some(&win_guid as *const windows::core::GUID) + } + None => None, + }; + + let etw_related_id = match related_id { + Some(guid) => { + let win_guid = abi_guid_to_winguid(guid); + Some(&win_guid as *const windows::core::GUID) + } + None => None, + }; + + 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 result = unsafe { + Etw::EventWriteTransfer( + Etw::REGHANDLE(reg_handle as i64), + &etw_descriptor, + etw_activity_id, + etw_related_id, + op_descriptor, + ) + }; + + Ok(result) + } + + fn event_set_information( + reg_handle: u64, + information_class: u32, + information: &Vec, + ) -> 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) + } + + 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/lib.rs b/rust/sdk/crates/libs/sdk-host/src/lib.rs index 47e9a72d..c421ab88 100644 --- a/rust/sdk/crates/libs/sdk-host/src/lib.rs +++ b/rust/sdk/crates/libs/sdk-host/src/lib.rs @@ -1,7 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#![allow(dead_code)] +mod etw; mod host_ffi; +use veil_abi_host_gen::export_interface; // 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. @@ -9,3 +12,28 @@ mod host_ffi; // KCM surface. In practice, most consumers only need // KeyCredentialManager::GetSecureId(). pub use userboundkey_kcm::KeyCredentialManager; + +/// Registers ETW providers and VTL0 callbacks for the given enclave. +pub fn register_sdk_callbacks( + enclave: *mut core::ffi::c_void, +) -> Result<(), veil_abi_host_gen::AbiError> { + let host = export_interface::new(enclave); + + host.register_vtl0_callbacks::()?; + host.register_etw_providers()?; + Ok(()) +} + +/// Unregisters ETW providers associated with the given enclave. +// TODO: This should be called during enclave teardown to unregister ETW providers. +// Once we have a enclave smart pointer. We can call this function during the drop +// implementation. For now, we will just expose this function to be called manually +// by the user of the sdk-host crate. +pub fn unregister_etw_providers( + enclave: *mut core::ffi::c_void, +) -> Result<(), veil_abi_host_gen::AbiError> { + let host = export_interface::new(enclave); + + host.unregister_etw_providers()?; + Ok(()) +} diff --git a/rust/sdk/generate_codegen_for_workspace.ps1 b/rust/sdk/generate_codegen_for_workspace.ps1 index ae47bf29..ad46c527 100644 --- a/rust/sdk/generate_codegen_for_workspace.ps1 +++ b/rust/sdk/generate_codegen_for_workspace.ps1 @@ -6,9 +6,10 @@ $errorActionPreference = "Stop" . "$PSScriptRoot\..\scripts\get_common_paths.ps1" # SDK crate paths -$edlPath = Join-Path $repoRoot "src\VbsEnclaveSDK\veil_abi.edl" +$edlPath = Join-Path $SdkWorkspacePath "crates/libs/edl/veil_abi.edl" $enclaveSdkCrate = Join-Path $SdkWorkspacePath "crates/libs/sdk-enclave" $hostSdkCrate = Join-Path $SdkWorkspacePath "crates/libs/sdk-host" +$importDir = Join-Path $SdkWorkspacePath "crates/libs/edl/" # Generate edlcodegen crates for the sdk-host and sdk-enclave crates . "$scriptsDir\generate_codegen_crates.ps1" ` @@ -16,7 +17,8 @@ $hostSdkCrate = Join-Path $SdkWorkspacePath "crates/libs/sdk-host" -EnclaveOutDir "$enclaveSdkCrate\generated" ` -EdlPath $edlPath ` -Namespace "veil_abi" ` - -Vtl0ClassName "export_interface" + -Vtl0ClassName "export_interface" ` + -ImportDirectories $importDir # Below this comment, Call the generate_codegen_crates.ps1 script # on any other crates in the sdk workspace that need codegen bindings. diff --git a/src/ToolingSharedLibrary/CurrentCodeGenerationState/Rust/Enclave/code_gen_test_gen/src/abi/abi_types.rs b/src/ToolingSharedLibrary/CurrentCodeGenerationState/Rust/Enclave/code_gen_test_gen/src/abi/abi_types.rs index 607995a8..c7340bcc 100644 --- a/src/ToolingSharedLibrary/CurrentCodeGenerationState/Rust/Enclave/code_gen_test_gen/src/abi/abi_types.rs +++ b/src/ToolingSharedLibrary/CurrentCodeGenerationState/Rust/Enclave/code_gen_test_gen/src/abi/abi_types.rs @@ -13,6 +13,7 @@ use crate::abi::fb_support::fb_types::edl::WStringT; use edlcodegen_enclave::EdlDerive; +#[repr(C)] #[derive(Debug, Clone, PartialEq, Default, EdlDerive)] #[target_struct(flatbuffer_types::FuncWithAllArgs_0_ArgsT)] pub struct FuncWithAllArgs_0_Args { @@ -32,6 +33,7 @@ pub struct FuncWithAllArgs_0_Args { pub m__return_value_: i32, } +#[repr(C)] #[derive(Debug, Clone, PartialEq, Default, EdlDerive)] #[target_struct(flatbuffer_types::FuncWithAllArgs_1_ArgsT)] pub struct FuncWithAllArgs_1_Args { diff --git a/src/ToolingSharedLibrary/CurrentCodeGenerationState/Rust/Enclave/code_gen_test_gen/src/implementation/types.rs b/src/ToolingSharedLibrary/CurrentCodeGenerationState/Rust/Enclave/code_gen_test_gen/src/implementation/types.rs index 15040a59..cbd2af6a 100644 --- a/src/ToolingSharedLibrary/CurrentCodeGenerationState/Rust/Enclave/code_gen_test_gen/src/implementation/types.rs +++ b/src/ToolingSharedLibrary/CurrentCodeGenerationState/Rust/Enclave/code_gen_test_gen/src/implementation/types.rs @@ -20,6 +20,7 @@ pub enum TestEnum { Value1 = 1, } +#[repr(C)] #[derive(Debug, Clone, PartialEq, Default, EdlDerive)] #[target_struct(flatbuffer_types::TestStruct1T)] pub struct TestStruct1 { @@ -28,6 +29,7 @@ pub struct TestStruct1 { pub array1: [i64;5], } +#[repr(C)] #[derive(Debug, Clone, PartialEq, Default, EdlDerive)] #[target_struct(flatbuffer_types::TestStruct2T)] pub struct TestStruct2 { diff --git a/src/ToolingSharedLibrary/CurrentCodeGenerationState/Rust/HostApp/code_gen_test_gen/src/abi/abi_types.rs b/src/ToolingSharedLibrary/CurrentCodeGenerationState/Rust/HostApp/code_gen_test_gen/src/abi/abi_types.rs index 048fa8e8..67d6b995 100644 --- a/src/ToolingSharedLibrary/CurrentCodeGenerationState/Rust/HostApp/code_gen_test_gen/src/abi/abi_types.rs +++ b/src/ToolingSharedLibrary/CurrentCodeGenerationState/Rust/HostApp/code_gen_test_gen/src/abi/abi_types.rs @@ -11,6 +11,7 @@ use crate::abi::fb_support::fb_types::edl::WStringT; use edlcodegen_host::EdlDerive; +#[repr(C)] #[derive(Debug, Clone, PartialEq, Default, EdlDerive)] #[target_struct(flatbuffer_types::FuncWithAllArgs_0_ArgsT)] pub struct FuncWithAllArgs_0_Args { @@ -30,6 +31,7 @@ pub struct FuncWithAllArgs_0_Args { pub m__return_value_: i32, } +#[repr(C)] #[derive(Debug, Clone, PartialEq, Default, EdlDerive)] #[target_struct(flatbuffer_types::FuncWithAllArgs_1_ArgsT)] pub struct FuncWithAllArgs_1_Args { diff --git a/src/ToolingSharedLibrary/CurrentCodeGenerationState/Rust/HostApp/code_gen_test_gen/src/implementation/types.rs b/src/ToolingSharedLibrary/CurrentCodeGenerationState/Rust/HostApp/code_gen_test_gen/src/implementation/types.rs index 02d0db17..4f1f3b91 100644 --- a/src/ToolingSharedLibrary/CurrentCodeGenerationState/Rust/HostApp/code_gen_test_gen/src/implementation/types.rs +++ b/src/ToolingSharedLibrary/CurrentCodeGenerationState/Rust/HostApp/code_gen_test_gen/src/implementation/types.rs @@ -18,6 +18,7 @@ pub enum TestEnum { Value1 = 1, } +#[repr(C)] #[derive(Debug, Clone, PartialEq, Default, EdlDerive)] #[target_struct(flatbuffer_types::TestStruct1T)] pub struct TestStruct1 { @@ -26,6 +27,7 @@ pub struct TestStruct1 { pub array1: [i64;5], } +#[repr(C)] #[derive(Debug, Clone, PartialEq, Default, EdlDerive)] #[target_struct(flatbuffer_types::TestStruct2T)] pub struct TestStruct2 { diff --git a/src/ToolingSharedLibrary/Includes/CodeGeneration/Rust/Constants.h b/src/ToolingSharedLibrary/Includes/CodeGeneration/Rust/Constants.h index c33a7680..1108190f 100644 --- a/src/ToolingSharedLibrary/Includes/CodeGeneration/Rust/Constants.h +++ b/src/ToolingSharedLibrary/Includes/CodeGeneration/Rust/Constants.h @@ -163,6 +163,7 @@ pub mod fb_types{{ inline constexpr std::string_view c_struct_attributes = R"( +#[repr(C)] #[derive(Debug, Clone, PartialEq, Default, EdlDerive)] #[target_struct(flatbuffer_types::{}T)] )"; From 53b11b229899de6b71b635c80ceb66510662afff Mon Sep 17 00:00:00 2001 From: Branden Bonaby <105318831+bbonaby@users.noreply.github.com> Date: Thu, 22 Jan 2026 16:28:43 -0800 Subject: [PATCH 2/5] fix cargo test run --- rust/sdk/crates/libs/sdk-enclave/src/lib.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/rust/sdk/crates/libs/sdk-enclave/src/lib.rs b/rust/sdk/crates/libs/sdk-enclave/src/lib.rs index 62865210..072ca12d 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/lib.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/lib.rs @@ -5,10 +5,13 @@ #![allow(dead_code)] #![allow(non_snake_case)] +// Unconditionally use no_std during non-cargo testing. +// While allowing std for cargo unit/integration tests. +#[cfg(test)] +extern crate std; +mod enclave_ffi; use veil_abi_enclave_gen::export_enclave_functions; extern crate alloc; - -mod enclave_ffi; pub mod etw; // Re-export Uuid from the uuid crate for use in enclave code. From f0a0f2e0249a716d08a7541d0dde3a441cce8ccb Mon Sep 17 00:00:00 2001 From: Branden Bonaby <105318831+bbonaby@users.noreply.github.com> Date: Thu, 22 Jan 2026 18:24:03 -0800 Subject: [PATCH 3/5] remove enclave crates from cargo tests --- .github/workflows/rust_ci.yml | 9 ++++++++- rust/sdk/crates/libs/sdk-enclave/src/lib.rs | 4 ---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/rust_ci.yml b/.github/workflows/rust_ci.yml index e79fa3cd..af1204c8 100644 --- a/.github/workflows/rust_ci.yml +++ b/.github/workflows/rust_ci.yml @@ -97,4 +97,11 @@ jobs: - name: test SDK working-directory: ./rust/sdk - run: cargo test --workspace + # Don't test the enclave crates here as they require special setup. (vertdll + veinterop can't be used outside of an enclave). + # We will need to do real integration tests for those later. + run: > + cargo test + --workspace + --exclude vbsenclave-sdk-enclave + --exclude windows-enclave + --exclude veil_abi_enclave_gen \ No newline at end of file diff --git a/rust/sdk/crates/libs/sdk-enclave/src/lib.rs b/rust/sdk/crates/libs/sdk-enclave/src/lib.rs index 072ca12d..fb0f97aa 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/lib.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/lib.rs @@ -5,10 +5,6 @@ #![allow(dead_code)] #![allow(non_snake_case)] -// Unconditionally use no_std during non-cargo testing. -// While allowing std for cargo unit/integration tests. -#[cfg(test)] -extern crate std; mod enclave_ffi; use veil_abi_enclave_gen::export_enclave_functions; extern crate alloc; From f5f85485d2510f6af41d129e1aa34e9616aa120b Mon Sep 17 00:00:00 2001 From: Branden Bonaby <105318831+bbonaby@users.noreply.github.com> Date: Fri, 23 Jan 2026 19:26:18 -0800 Subject: [PATCH 4/5] Add updates after userboundkey merge --- .../crates/samples/helloworld/Cargo.toml | 1 + .../samples/helloworld/enclave/Cargo.toml | 3 +- .../enclave/src/enclave_constants.rs | 32 --- .../samples/helloworld/enclave/src/lib.rs | 14 +- .../samples/helloworld/host/src/container.rs | 132 --------- .../helloworld/host/src/enclave_api_ffi.rs | 47 ---- .../samples/helloworld/host/src/main.rs | 27 +- rust/sdk/crates/etw.md | 27 +- rust/sdk/crates/libs/{ => edl/sdk}/sdk.edl | 4 + .../tracelogging.edl} | 0 rust/sdk/crates/libs/edl/veil_abi.edl | 15 -- rust/sdk/crates/libs/sdk-enclave/Cargo.toml | 3 +- .../libs/sdk-enclave/src/common/sdk_impls.rs | 35 ++- .../libs/sdk-enclave/src/common/utils.rs | 24 ++ .../libs/sdk-enclave/src/enclave_ffi.rs | 2 - rust/sdk/crates/libs/sdk-enclave/src/etw.rs | 130 ++++----- rust/sdk/crates/libs/sdk-enclave/src/lib.rs | 33 ++- .../sdk-enclave/src/userboundkey/types.rs | 22 +- rust/sdk/crates/libs/sdk-host/Cargo.toml | 1 + .../crates/libs/sdk-host/src/common/mod.rs | 5 + .../libs/sdk-host/src/common/sdk_impls.rs | 47 +++- rust/sdk/crates/libs/sdk-host/src/enclave.rs | 36 ++- rust/sdk/crates/libs/sdk-host/src/etw.rs | 250 +++++++++--------- rust/sdk/crates/libs/sdk-host/src/host_ffi.rs | 2 - rust/sdk/crates/libs/sdk-host/src/lib.rs | 20 +- .../samples/userboundkey/enclave/src/lib.rs | 2 + .../samples/userboundkey/host/src/main.rs | 20 +- rust/sdk/generate_codegen_for_workspace.ps1 | 6 +- 28 files changed, 431 insertions(+), 509 deletions(-) delete mode 100644 rust/edlcodegen/crates/samples/helloworld/enclave/src/enclave_constants.rs delete mode 100644 rust/edlcodegen/crates/samples/helloworld/host/src/container.rs delete mode 100644 rust/edlcodegen/crates/samples/helloworld/host/src/enclave_api_ffi.rs rename rust/sdk/crates/libs/{ => edl/sdk}/sdk.edl (97%) rename rust/sdk/crates/libs/edl/{veil_tracelogging.edl => sdk/tracelogging.edl} (100%) delete mode 100644 rust/sdk/crates/libs/edl/veil_abi.edl delete mode 100644 rust/sdk/crates/libs/sdk-enclave/src/enclave_ffi.rs delete mode 100644 rust/sdk/crates/libs/sdk-host/src/host_ffi.rs diff --git a/rust/edlcodegen/crates/samples/helloworld/Cargo.toml b/rust/edlcodegen/crates/samples/helloworld/Cargo.toml index 13ce1710..e90029a9 100644 --- a/rust/edlcodegen/crates/samples/helloworld/Cargo.toml +++ b/rust/edlcodegen/crates/samples/helloworld/Cargo.toml @@ -19,3 +19,4 @@ 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 8a176a16..32ff9173 100644 --- a/rust/edlcodegen/crates/samples/helloworld/enclave/Cargo.toml +++ b/rust/edlcodegen/crates/samples/helloworld/enclave/Cargo.toml @@ -14,4 +14,5 @@ edlcodegen-tools = { workspace = true } # 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 } \ No newline at end of file +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/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 0e4492b2..f92ddd82 100644 --- a/rust/edlcodegen/crates/samples/helloworld/enclave/src/lib.rs +++ b/rust/edlcodegen/crates/samples/helloworld/enclave/src/lib.rs @@ -4,10 +4,11 @@ #![no_std] extern crate alloc; mod edl_impls; -mod enclave_constants; -use enclave_constants::*; 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. @@ -24,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))] @@ -68,9 +73,6 @@ pub extern "system" fn DllMain( reason: u32, _reserved: *mut core::ffi::c_void,) -> bool { - // Export SDK enclave functions from dll. - vbsenclave_sdk_enclave::export_enclave_functions(); - // DLL_PROCESS_ATTACH if reason == 1 { vbsenclave_sdk_enclave::etw::add_provider(&HELLO_WORLD_PROVIDER); 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 c0f8cacf..00000000 --- a/rust/edlcodegen/crates/samples/helloworld/host/src/container.rs +++ /dev/null @@ -1,132 +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 { - // Explicitly unregister ETW providers before unloading the enclave. - let _ = vbsenclave_sdk_host::unregister_etw_providers(enclave); - - 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 ddf89467..a2588ac2 100644 --- a/rust/edlcodegen/crates/samples/helloworld/host/src/main.rs +++ b/rust/edlcodegen/crates/samples/helloworld/host/src/main.rs @@ -3,22 +3,37 @@ 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::() { return Err(format!("Failed to register VTL0 callbacks: HRESULT: {:x}", err.to_hresult().0).into()); } - if let Err(err) = vbsenclave_sdk_host::register_sdk_callbacks(enclave_container.enclave) { + // 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()); } diff --git a/rust/sdk/crates/etw.md b/rust/sdk/crates/etw.md index afff7e16..3d9955fe 100644 --- a/rust/sdk/crates/etw.md +++ b/rust/sdk/crates/etw.md @@ -2,7 +2,7 @@ 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 unregistration, and transparently forwards ETW calls between the +provider registration and deregistration, and transparently forwards ETW calls between the enclave and the host. No additional integration steps are required. ## Enclave Usage @@ -37,14 +37,18 @@ 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 { - // Export SDK enclave functions - vbsenclave_sdk_enclave::export_enclave_functions(); + // Add a provider or list of providers. vbsenclave_sdk_enclave::etw::add_provider(&HELLO_WORLD_PROVIDER); } @@ -64,12 +68,27 @@ 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)?; +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 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/veil_tracelogging.edl b/rust/sdk/crates/libs/edl/sdk/tracelogging.edl similarity index 100% rename from rust/sdk/crates/libs/edl/veil_tracelogging.edl rename to rust/sdk/crates/libs/edl/sdk/tracelogging.edl diff --git a/rust/sdk/crates/libs/edl/veil_abi.edl b/rust/sdk/crates/libs/edl/veil_abi.edl deleted file mode 100644 index 5a680828..00000000 --- a/rust/sdk/crates/libs/edl/veil_abi.edl +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -enclave -{ - import "veil_tracelogging.edl"; - - trusted - { - }; - - untrusted - { - }; -}; diff --git a/rust/sdk/crates/libs/sdk-enclave/Cargo.toml b/rust/sdk/crates/libs/sdk-enclave/Cargo.toml index a3307501..d54eb525 100644 --- a/rust/sdk/crates/libs/sdk-enclave/Cargo.toml +++ b/rust/sdk/crates/libs/sdk-enclave/Cargo.toml @@ -6,7 +6,8 @@ edition = { workspace = true } [dependencies] sdk_enclave_gen = { path = "./generated/sdk_gen" } spin = { workspace = true } -veil_abi_enclave_gen = { path = "./generated/veil_abi_gen" } +tracelogging = { workspace = true, features = ["etw", "kernel_mode"] } +uuid = { workspace = true, default-features = false, features = [ "v4"] } widestring = { 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 index 4b19a3a5..9d4f8274 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/etw.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/etw.rs @@ -1,16 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use crate::common; use alloc::{collections::BTreeMap, vec::Vec}; use core::marker::PhantomData; -use core::mem; +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}; -use veil_abi_enclave_gen::AbiError; -use veil_abi_enclave_gen::implementation::trusted::Trusted; -use veil_abi_enclave_gen::implementation::types as edl_types; -use veil_abi_enclave_gen::stubs::untrusted::{self}; -use windows_enclave::vertdll; /// Information about a registered ETW provider. /// The registration_id is generated during registration @@ -38,7 +36,7 @@ fn etw_providers() -> &'static RwLock> { ETW_PROVIDERS.call_once(|| RwLock::new(Vec::new())) } -fn register_providers() { +pub fn register_providers() { static REGISTER_PROVIDERS_ONCE: Once<()> = Once::new(); REGISTER_PROVIDERS_ONCE.call_once(|| { for &provider in etw_providers().read().iter() { @@ -49,14 +47,12 @@ fn register_providers() { }); } -/// Public function to be called by developer at dll load time. /// 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); } -/// Public function to be called by developer at dll load time. /// 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]) { @@ -66,7 +62,6 @@ pub fn add_providers(providers: &[&'static Provider]) { } } -/// Public function to be called by developer at dll unload time. /// Unregisters all ETW providers added via `add_provider` or `add_providers`. pub fn unregister_providers() { for provider in etw_providers().read().iter() { @@ -123,7 +118,6 @@ fn result_from_abi(result: Result) -> u32 { AbiError::Win32Error(code) => code, // Can't convert HRESULT to Win32 error code directly, // return a generic error code. - // TODO: improve this mapping if needed. AbiError::Hresult(_) => 0x0000054F_u32, //ERROR_INTERNAL_ERROR } } @@ -178,19 +172,15 @@ unsafe extern "system" fn EtwRegister( map.table.insert(registration_id, info); } - let mut enclave_info = vertdll::ENCLAVE_INFORMATION::default(); - - let hr = unsafe { - vertdll::EnclaveGetEnclaveInformation( - mem::size_of::() as u32, - &mut enclave_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 + } }; - if hr != 0 { - return 0x0000054F_u32; // ERROR_INTERNAL_ERROR - } - let result = untrusted::event_register( &uuid_to_abi(&uuid_prov_id), &uuid_to_abi(®istration_id), @@ -307,62 +297,48 @@ unsafe extern "system" fn EtwActivityIdControl( result_from_abi(result) } -pub struct EnclaveImpl {} - -impl Trusted for EnclaveImpl { - 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, - }; +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, + ); - // 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(()) - } + 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, + }; - fn register_etw_providers() -> Result<(), AbiError> { - register_providers(); - Ok(()) - } + // 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, + ); - fn unregister_etw_providers() -> Result<(), AbiError> { - unregister_providers(); - Ok(()) - } + Ok(()) } diff --git a/rust/sdk/crates/libs/sdk-enclave/src/lib.rs b/rust/sdk/crates/libs/sdk-enclave/src/lib.rs index aee36727..e8049e4d 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/lib.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/lib.rs @@ -4,18 +4,16 @@ #![no_std] #![allow(dead_code)] #![allow(non_snake_case)] - -mod enclave_ffi; +extern crate alloc; 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 Uuid from the uuid crate for use in enclave code. -pub use uuid::Uuid; +// 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. /// @@ -38,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 d5bbf2dc..e4861cfe 100644 --- a/rust/sdk/crates/libs/sdk-host/Cargo.toml +++ b/rust/sdk/crates/libs/sdk-host/Cargo.toml @@ -21,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 index 61f10efb..855f772b 100644 --- a/rust/sdk/crates/libs/sdk-host/src/etw.rs +++ b/rust/sdk/crates/libs/sdk-host/src/etw.rs @@ -1,11 +1,10 @@ // 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 veil_abi_host_gen::AbiError; -use veil_abi_host_gen::export_interface; -use veil_abi_host_gen::implementation::types as codegen_types; -use veil_abi_host_gen::implementation::untrusted::Untrusted; use windows::Win32::System::Diagnostics::Etw; #[repr(C)] @@ -71,8 +70,8 @@ unsafe extern "system" fn etw_call_back( let context = unsafe { &*(callback_context as *const EtwProviderContext) }; // At this point edlcodegen callback infrastructure is already registered. So, we can create - // the interface to call the etw passthrough callback. - let interface = export_interface::new(context.enclave as *mut core::ffi::c_void); + // 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. @@ -100,7 +99,7 @@ unsafe extern "system" fn etw_call_back( } // send callback data back to enclave - let _ = interface.etw_callback_passthrough( + let _ = host.etw_callback_passthrough( &context.registration_id, &abi_source_guid, is_enabled.0, @@ -111,139 +110,134 @@ unsafe extern "system" fn etw_call_back( ); } -pub struct HostImpl {} - -impl Untrusted for HostImpl { - fn event_unregister(reg_handle: u64) -> Result { - let result = unsafe { Etw::EventUnregister(Etw::REGHANDLE(reg_handle as i64)) }; - Ok(result) - } - - 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"); - - etw_context.list.push(EtwProviderContext { - provider_id: provider_id.clone(), - registration_id: registration_id.clone(), - enclave, - }); - - let context_entry = etw_context.list.last().unwrap(); - let last_entry_pvoid = context_entry as *const _ as *const core::ffi::c_void; - - let result = unsafe { - let mut handle = Etw::REGHANDLE(0); - let res_code = Etw::EventRegister( - &guid, - Some(etw_call_back), - Some(last_entry_pvoid), - &mut handle, - ); - - *reg_handle = handle.0 as u64; - res_code - }; - - Ok(result) - } +pub fn event_unregister(reg_handle: u64) -> Result { + let result = unsafe { Etw::EventUnregister(Etw::REGHANDLE(reg_handle as i64)) }; + Ok(result) +} - fn event_write_transfer( - reg_handle: u64, - descriptor: &codegen_types::EventDescriptor, - activity_id: &Option, - related_id: &Option, - user_data: &Vec, - ) -> 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, - }; +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"); + + etw_context.list.push(EtwProviderContext { + provider_id: provider_id.clone(), + registration_id: registration_id.clone(), + enclave, + }); + + let context_entry = etw_context.list.last().unwrap(); + let last_entry_pvoid = context_entry as *const _ as *const core::ffi::c_void; + + let result = unsafe { + let mut handle = Etw::REGHANDLE(0); + let res_code = Etw::EventRegister( + &guid, + Some(etw_call_back), + Some(last_entry_pvoid), + &mut handle, + ); + + *reg_handle = handle.0 as u64; + res_code + }; - let etw_activity_id = match activity_id { - Some(guid) => { - let win_guid = abi_guid_to_winguid(guid); - Some(&win_guid as *const windows::core::GUID) - } - None => None, - }; + Ok(result) +} - let etw_related_id = match related_id { - Some(guid) => { - let win_guid = abi_guid_to_winguid(guid); - Some(&win_guid as *const windows::core::GUID) - } - None => None, - }; +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 etw_activity_id = match activity_id { + Some(guid) => { + let win_guid = abi_guid_to_winguid(guid); + Some(&win_guid as *const windows::core::GUID) } + None => None, + }; - let mut op_descriptor: Option<&[Etw::EVENT_DATA_DESCRIPTOR]> = None; - if !descriptors.is_empty() { - op_descriptor = Some(descriptors.as_slice()); + let etw_related_id = match related_id { + Some(guid) => { + let win_guid = abi_guid_to_winguid(guid); + Some(&win_guid as *const windows::core::GUID) } + None => None, + }; - let result = unsafe { - Etw::EventWriteTransfer( - Etw::REGHANDLE(reg_handle as i64), - &etw_descriptor, - etw_activity_id, - etw_related_id, - op_descriptor, - ) - }; + 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, + }, + }); + } - Ok(result) + let mut op_descriptor: Option<&[Etw::EVENT_DATA_DESCRIPTOR]> = None; + if !descriptors.is_empty() { + op_descriptor = Some(descriptors.as_slice()); } - fn event_set_information( - reg_handle: u64, - information_class: u32, - information: &Vec, - ) -> 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, - ) - }; + let result = unsafe { + Etw::EventWriteTransfer( + Etw::REGHANDLE(reg_handle as i64), + &etw_descriptor, + etw_activity_id, + etw_related_id, + op_descriptor, + ) + }; - Ok(result) - } + 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) +} - 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) }; +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) - } + *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 da239f91..3351401a 100644 --- a/rust/sdk/crates/libs/sdk-host/src/lib.rs +++ b/rust/sdk/crates/libs/sdk-host/src/lib.rs @@ -2,18 +2,13 @@ // Licensed under the MIT License. #![allow(dead_code)] -mod etw; -mod host_ffi; - 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. @@ -26,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 /// @@ -46,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 Date: Mon, 26 Jan 2026 14:03:43 -0800 Subject: [PATCH 5/5] update based on comments --- rust/sdk/crates/etw.md | 17 ++++++- rust/sdk/crates/libs/sdk-enclave/src/etw.rs | 10 ++++- rust/sdk/crates/libs/sdk-host/src/etw.rs | 49 +++++++++------------ 3 files changed, 44 insertions(+), 32 deletions(-) diff --git a/rust/sdk/crates/etw.md b/rust/sdk/crates/etw.md index 3d9955fe..ae9a1792 100644 --- a/rust/sdk/crates/etw.md +++ b/rust/sdk/crates/etw.md @@ -5,7 +5,14 @@ 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. -## Enclave Usage +> [!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 @@ -56,7 +63,7 @@ pub extern "system" fn DllMain(instance: *const c_void, reason: u32, reserved: * } ``` -## Host Usage +## Inside your Host ### 1. Add dependencies @@ -97,3 +104,9 @@ 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-enclave/src/etw.rs b/rust/sdk/crates/libs/sdk-enclave/src/etw.rs index 9d4f8274..0897f634 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/etw.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/etw.rs @@ -181,14 +181,20 @@ unsafe extern "system" fn EtwRegister( } }; - let result = untrusted::event_register( + 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, ); - result_from_abi(result) + 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. diff --git a/rust/sdk/crates/libs/sdk-host/src/etw.rs b/rust/sdk/crates/libs/sdk-host/src/etw.rs index 855f772b..594cd9a9 100644 --- a/rust/sdk/crates/libs/sdk-host/src/etw.rs +++ b/rust/sdk/crates/libs/sdk-host/src/etw.rs @@ -16,7 +16,9 @@ pub struct EtwProviderContext { #[derive(Default)] pub struct EtwContextList { - list: Vec, + #[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(); @@ -38,6 +40,11 @@ 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 @@ -126,23 +133,19 @@ pub fn event_register( .write() .expect("Failed to acquire ETW context list write lock"); - etw_context.list.push(EtwProviderContext { + let boxed_context = Box::new(EtwProviderContext { provider_id: provider_id.clone(), registration_id: registration_id.clone(), enclave, }); - let context_entry = etw_context.list.last().unwrap(); - let last_entry_pvoid = context_entry as *const _ as *const core::ffi::c_void; + 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(last_entry_pvoid), - &mut handle, - ); + let res_code = + Etw::EventRegister(&guid, Some(etw_call_back), Some(context_ptr), &mut handle); *reg_handle = handle.0 as u64; res_code @@ -168,22 +171,6 @@ pub fn event_write_transfer( Keyword: descriptor.keyword, }; - let etw_activity_id = match activity_id { - Some(guid) => { - let win_guid = abi_guid_to_winguid(guid); - Some(&win_guid as *const windows::core::GUID) - } - None => None, - }; - - let etw_related_id = match related_id { - Some(guid) => { - let win_guid = abi_guid_to_winguid(guid); - Some(&win_guid as *const windows::core::GUID) - } - None => None, - }; - let mut descriptors: Vec = Vec::with_capacity(user_data.len()); for desc in user_data.iter() { descriptors.push(Etw::EVENT_DATA_DESCRIPTOR { @@ -200,12 +187,18 @@ pub fn event_write_transfer( 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, - etw_activity_id, - etw_related_id, + win_activity_id_ptr, + win_related_id_ptr, op_descriptor, ) };