diff --git a/Cargo.lock b/Cargo.lock index eff4d29..8172b3d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -256,6 +256,17 @@ dependencies = [ "wdk-sys", ] +[[package]] +name = "fail_driver_pool_leak" +version = "0.1.0" +dependencies = [ + "wdk", + "wdk-alloc", + "wdk-build", + "wdk-panic", + "wdk-sys", +] + [[package]] name = "glob" version = "0.3.1" diff --git a/Cargo.toml b/Cargo.toml index 847728b..b4fdc8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "general/echo/kmdf/driver/*", "general/echo/kmdf/exe", "tools/dv/kmdf/fail_driver_irql_violation", + "tools/dv/kmdf/fail_driver_pool_leak", ] resolver = "2" diff --git a/tools/dv/kmdf/fail_driver_pool_leak/Cargo.toml b/tools/dv/kmdf/fail_driver_pool_leak/Cargo.toml new file mode 100644 index 0000000..62d674d --- /dev/null +++ b/tools/dv/kmdf/fail_driver_pool_leak/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "fail_driver_pool_leak" +version = "0.1.0" +edition.workspace = true +publish.workspace = true +repository.workspace = true +license.workspace = true + +[package.metadata.wdk] + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wdk.workspace = true +wdk-alloc.workspace = true +wdk-panic.workspace = true +wdk-sys.workspace = true + +[build-dependencies] +wdk-build.workspace = true + +[features] +default = [] +nightly = ["wdk/nightly", "wdk-sys/nightly"] diff --git a/tools/dv/kmdf/fail_driver_pool_leak/README.md b/tools/dv/kmdf/fail_driver_pool_leak/README.md new file mode 100644 index 0000000..52c89d9 --- /dev/null +++ b/tools/dv/kmdf/fail_driver_pool_leak/README.md @@ -0,0 +1,94 @@ +# Fail_Driver_Pool_Leak Sample + +This sample KMDF Fail Driver demonstrates the capabilities and features of **Driver Verifier** and the **Device Fundamentals Tests**. + +It allocates a pool of memory to a global buffer when a supported device is added by the PnP Manager and intentionally does not free it before the driver is unloaded. This memory leak fault is a system vulnerability that could lead to security and performance issues and bad user experience. + +By enabling Driver Verifier on this driver, this pool leak violation can be caught before the driver is unloaded and with an active KDNET session, the bug can be analyzed further. + +NOTE: The driver uses WDM's ExAllocatePool2 API directly to allocate memory for its buffer. Ideally, such allocations should be freed by using ExFreePool API. A cleaner way to manage memory in a WDF Driver is to use [wdfmemory](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdfmemory/) + + +## Steps to reproduce the issue + +1. Clone the repository and navigate to the project root. + +2. Build the driver project using the following command in a WDK environment (or EWDK prompt) - + ``` + cargo make + ``` +3. Prepare a target system (a Hyper-V VM can be used) for testing + + Follow the below steps to setup the test system - + 1. Disable Secure boot and start the system + 2. Run "ipconfig" on the host system and note down the IP (if you are using Default Switch for the VM, note down the IP on the Default Switch) + 3. Install and open WinDbg, click on "Attach to Kernel". The key for the connection will be generated in the test system in the next steps. + 4. Connect to the test VM and run the following commands - + ``` + bcdedit /set testsigning on + bcdedit /debug on + bcdedit /dbgsettings net hostip: port:<50000-50030> + + ### Copy the key string output by the above command + ``` + 5. Paste the key in host's WinDbg prompt and connect to the kernel + 6. Restart the target/test system + ``` + shutdown -r -t 0 + ``` + +4. Copy the driver package, available under ".\target\debug\fail_driver_pool_leak_package" to the target system. + +5. Copy "devgen.exe" from host to the target system. Alternatively you may install WDK on the target system and add the directory that contains "devgen.exe" to PATH variable. + +6. Install the driver package and create the device in the target system using the below commands - + ``` + cd "fail_driver_pool_leak_package" + devgen.exe /add /bus ROOT /hardwareid "fail_driver_pool_leak" + + ## Copy the Device ID. This will be used later to run the tests + + pnputil.exe /add-driver .\fail_driver_pool_leak.inf /install + ``` +7. Enable Driver Verifier for 'fail_driver_pool_leak.sys' driver package + 1. Open run command prompt (Start + R) or cmd as administator and run "verifier" + 2. In the verifier manager, + - Create Standard Settings + - Select driver names from list + - Select 'fail_driver_pool_leak.sys' + - Finish + - Restart the system + +8. Follow the steps in https://learn.microsoft.com/en-us/windows-hardware/drivers/develop/how-to-test-a-driver-at-runtime-from-a-command-prompt to run tests against the device managed by this driver + +9. Install TAEF and WDTF on the test computer and run the following test - + ``` + cd "C:\Program Files (x86)\Windows Kits\10\Testing\Tests\Additional Tests\x64\DevFund" + TE.exe .\Devfund_PnPDTest_WLK_Certification.dll /P:"DQ=DeviceID='ROOT\DEVGEN\{PASTE-DEVICE-ID-HERE}'" --rebootResumeOption:Manual + ``` + +10. The test will lead to a Bugcheck and a BlueScreen on the target system with the following error - + ``` + DRIVER_VERIFIER_DETECTED_VIOLATION (c4) + ``` + The logs will be available in WinDbg + run ```!analyze -v``` for detailed bugcheck report + run ```!verifier 3 fail_driver_pool_leak.sys``` for info on the allocations that were leaked that caused the bugcheck. + +11. (Alternatively), the bugcheck can be observed when all the devices managed by this driver are removed, i.e, when the driver is unloaded from the system. + You may use pnputil/devcon to enumerate and remove the devices - + ``` + # To enumerate the devices + pnputil /enum-devices + # To remove a device + pnputil /remove-device "DEVICE-ID" + ``` + +### References + +- [Driver Verifier](https://learn.microsoft.com/en-us/windows-hardware/drivers/devtest/driver-verifier) +- [Device Fundamentals Tests](https://learn.microsoft.com/en-us/windows-hardware/drivers/devtest/device-fundamentals-tests) +- [TAEF](https://learn.microsoft.com/en-us/windows-hardware/drivers/taef/getting-started) +- [WDTF](https://learn.microsoft.com/en-us/windows-hardware/drivers/wdtf/wdtf-runtime-library) +- [Testing a driver at runtime](https://learn.microsoft.com/en-us/windows-hardware/drivers/develop/how-to-test-a-driver-at-runtime-from-a-command-prompt) +- [Using WDF to Develop a Driver](https://learn.microsoft.com/en-us/windows-hardware/drivers/wdf/using-the-framework-to-develop-a-driver) diff --git a/tools/dv/kmdf/fail_driver_pool_leak/build.rs b/tools/dv/kmdf/fail_driver_pool_leak/build.rs new file mode 100644 index 0000000..ebbc7e2 --- /dev/null +++ b/tools/dv/kmdf/fail_driver_pool_leak/build.rs @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation +// License: MIT OR Apache-2.0 + +fn main() -> Result<(), wdk_build::ConfigError> { + wdk_build::Config::from_env_auto()?.configure_binary_build(); + Ok(()) +} diff --git a/tools/dv/kmdf/fail_driver_pool_leak/fail_driver_pool_leak.inx b/tools/dv/kmdf/fail_driver_pool_leak/fail_driver_pool_leak.inx new file mode 100644 index 0000000..ba33969 --- /dev/null +++ b/tools/dv/kmdf/fail_driver_pool_leak/fail_driver_pool_leak.inx @@ -0,0 +1,56 @@ +;=================================================================== +; Copyright (c)2023, Microsoft Corporation +; +;Module Name: +; FAIL_DRIVER_POOL_LEAK.INF +;=================================================================== + +[Version] +Signature = "$WINDOWS NT$" +Class = SoftwareComponent +ClassGuid = {5c4c3332-344d-483c-8739-259e934c9cc8} +Provider = %ProviderString% +PnpLockDown = 1 + +[DestinationDirs] +DefaultDestDir = 13 + +[SourceDisksNames] +1 = %DiskId1%,,,"" + +[SourceDisksFiles] +fail_driver_pool_leak.sys = 1,, + +; ================= Install section ================= + +[Manufacturer] +%StdMfg%=Standard,NT$ARCH$.10.0...16299 + +[Standard.NT$ARCH$.10.0...16299] +%FAIL_DRIVER_POOL_LEAK.DeviceDesc%=FAIL_DRIVER_POOL_LEAK_DEVICE, fail_driver_pool_leak + +[FAIL_DRIVER_POOL_LEAK_DEVICE.NT$ARCH$] +CopyFiles=Drivers_Dir + +[Drivers_Dir] +fail_driver_pool_leak.sys + +; ================= Service installation ================= +[FAIL_DRIVER_POOL_LEAK_Device.NT$ARCH$.Services] +AddService = fail_driver_pool_leak, %SPSVCINST_ASSOCSERVICE%, fail_driver_pool_leak_svc_ins + +[fail_driver_pool_leak_svc_ins] +DisplayName = %FAIL_DRIVER_POOL_LEAK.SVCDESC% +ServiceType = 1 ; SERVICE_KERNEL_DRIVER +StartType = 3 ; SERVICE_DEMAND_START +ErrorControl = 1 ; SERVICE_ERROR_NORMAL +ServiceBinary = %13%\fail_driver_pool_leak.sys + +; ================= Strings ================= +[Strings] +SPSVCINST_ASSOCSERVICE = 0x00000002 +ProviderString = "Rust-DV-Fail-Sample" +StdMfg = "(Standard system devices)" +DiskId1 = "WDF FAIL_DRIVER_POOL_LEAK Installation Disk #1" +FAIL_DRIVER_POOL_LEAK.DeviceDesc = "WDF FAIL_DRIVER_POOL_LEAK Device" +FAIL_DRIVER_POOL_LEAK.SVCDESC = "WDF FAIL_DRIVER_POOL_LEAK Service" \ No newline at end of file diff --git a/tools/dv/kmdf/fail_driver_pool_leak/src/driver.rs b/tools/dv/kmdf/fail_driver_pool_leak/src/driver.rs new file mode 100644 index 0000000..283e396 --- /dev/null +++ b/tools/dv/kmdf/fail_driver_pool_leak/src/driver.rs @@ -0,0 +1,197 @@ +// Copyright (c) Microsoft Corporation. +// License: MIT OR Apache-2.0 + +use wdk::{nt_success, paged_code, println}; +use wdk_sys::{ + macros, + ntddk::{ExAllocatePool2, KeGetCurrentIrql}, + APC_LEVEL, + DRIVER_OBJECT, + NTSTATUS, + PCUNICODE_STRING, + PDRIVER_OBJECT, + POOL_FLAG_NON_PAGED, + SIZE_T, + ULONG, + WDFDEVICE, + WDFDEVICE_INIT, + WDFDRIVER, + WDF_DRIVER_CONFIG, + WDF_NO_HANDLE, + WDF_NO_OBJECT_ATTRIBUTES, + WDF_OBJECT_ATTRIBUTES, + _WDF_EXECUTION_LEVEL, + _WDF_SYNCHRONIZATION_SCOPE, +}; + +use crate::{GLOBAL_BUFFER, GUID_DEVINTERFACE}; + +/// `DriverEntry` initializes the driver and is the first routine called by the +/// system after the driver is loaded. `DriverEntry` specifies the other entry +/// points in the function driver, such as `EvtDevice` and `DriverUnload`. +/// +/// # Arguments +/// +/// * `driver` - represents the instance of the function driver that is loaded +/// into memory. `DriverEntry` must initialize members of `DriverObject` +/// before it returns to the caller. `DriverObject` is allocated by the system +/// before the driver is loaded, and it is released by the system after the +/// system unloads the function driver from memory. +/// * `registry_path` - represents the driver specific path in the Registry. The +/// function driver can use the path to store driver related data between +/// reboots. The path does not store hardware instance specific data. +/// +/// # Return value: +/// +/// * `STATUS_SUCCESS` - if successful, +/// * `STATUS_UNSUCCESSFUL` - otherwise. +#[link_section = "INIT"] +#[export_name = "DriverEntry"] +extern "system" fn driver_entry( + driver: &mut DRIVER_OBJECT, + registry_path: PCUNICODE_STRING, +) -> NTSTATUS { + println!("Enter: driver_entry"); + + let mut driver_config = { + let wdf_driver_config_size: ULONG; + + // clippy::cast_possible_truncation cannot currently check compile-time constants: https://github.com/rust-lang/rust-clippy/issues/9613 + #[allow(clippy::cast_possible_truncation)] + { + const WDF_DRIVER_CONFIG_SIZE: usize = core::mem::size_of::(); + + // Manually assert there is not truncation since clippy doesn't work for + // compile-time constants + const { assert!(WDF_DRIVER_CONFIG_SIZE <= ULONG::MAX as usize) } + + wdf_driver_config_size = WDF_DRIVER_CONFIG_SIZE as ULONG; + } + + WDF_DRIVER_CONFIG { + Size: wdf_driver_config_size, + EvtDriverDeviceAdd: Some(evt_driver_device_add), + EvtDriverUnload: Some(evt_driver_unload), + ..WDF_DRIVER_CONFIG::default() + } + }; + + let driver_handle_output = WDF_NO_HANDLE.cast::(); + + let nt_status = unsafe { + macros::call_unsafe_wdf_function_binding!( + WdfDriverCreate, + driver as PDRIVER_OBJECT, + registry_path, + WDF_NO_OBJECT_ATTRIBUTES, + &mut driver_config, + driver_handle_output, + ) + }; + + if !nt_success(nt_status) { + println!("Error: WdfDriverCreate failed {nt_status:#010X}"); + return nt_status; + } + + println!("Exit: driver_entry"); + + nt_status +} + +/// `EvtDeviceAdd` is called by the framework in response to `AddDevice` +/// call from the `PnP` manager. We create and initialize a device object to +/// represent a new instance of the device. +/// +/// # Arguments: +/// +/// * `_driver` - Handle to a framework driver object created in `DriverEntry` +/// * `device_init` - Pointer to a framework-allocated `WDFDEVICE_INIT` +/// structure. +/// +/// # Return value: +/// +/// * `NTSTATUS` +#[link_section = "PAGE"] +extern "C" fn evt_driver_device_add( + _driver: WDFDRIVER, + mut device_init: *mut WDFDEVICE_INIT, +) -> NTSTATUS { + paged_code!(); + + println!("Enter: evt_driver_device_add"); + + #[allow(clippy::cast_possible_truncation)] + let mut attributes = WDF_OBJECT_ATTRIBUTES { + Size: core::mem::size_of::() as ULONG, + ExecutionLevel: _WDF_EXECUTION_LEVEL::WdfExecutionLevelInheritFromParent, + SynchronizationScope: _WDF_SYNCHRONIZATION_SCOPE::WdfSynchronizationScopeInheritFromParent, + ..WDF_OBJECT_ATTRIBUTES::default() + }; + + let mut device = WDF_NO_HANDLE as WDFDEVICE; + let mut nt_status = unsafe { + macros::call_unsafe_wdf_function_binding!( + WdfDeviceCreate, + &mut device_init, + &mut attributes, + &mut device, + ) + }; + + if !nt_success(nt_status) { + println!("Error: WdfDeviceCreate failed {nt_status:#010X}"); + return nt_status; + } + + // Allocate non-paged memory pool of 64 bytes (arbitrarily chosen) for the + // Global buffer. This pool of memory is intentionally not freed by + // the driver. + unsafe { + const LENGTH: usize = 64; + GLOBAL_BUFFER = ExAllocatePool2(POOL_FLAG_NON_PAGED, LENGTH as SIZE_T, 's' as u32); + } + + nt_status = unsafe { + macros::call_unsafe_wdf_function_binding!( + WdfDeviceCreateDeviceInterface, + device, + &GUID_DEVINTERFACE, + core::ptr::null_mut(), + ) + }; + + if !nt_success(nt_status) { + println!("Error: WdfDeviceCreateDeviceInterface failed {nt_status:#010X}"); + return nt_status; + } + + println!("Exit: evt_driver_device_add"); + + nt_status +} + +/// This event callback function is called before the driver is unloaded +/// +/// The EvtDriverUnload callback function must deallocate any +/// non-device-specific system resources that the driver's DriverEntry routine +/// allocated. +/// +/// # Argument: +/// +/// * `driver` - Handle to the framework driver object +/// +/// # Return Value: +/// +/// None +extern "C" fn evt_driver_unload(_driver: WDFDRIVER) { + println!("Enter: evt_driver_unload"); + + // Ideally, the memory allocated to the Global buffer in lib.rs L51 should + // be freed here by calling the ExFreePool API. But to demonstrate the Driver + // Verifier's ability to catch pool leaks, the buffer is deliberately not freed. + + // unsafe { wdk_sys::ntddk::ExFreePool(GLOBAL_BUFFER) }; + + println!("Exit: evt_driver_unload"); +} diff --git a/tools/dv/kmdf/fail_driver_pool_leak/src/lib.rs b/tools/dv/kmdf/fail_driver_pool_leak/src/lib.rs new file mode 100644 index 0000000..bc37d18 --- /dev/null +++ b/tools/dv/kmdf/fail_driver_pool_leak/src/lib.rs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. +// License: MIT OR Apache-2.0 + +//! # Abstract +//! +//! This KMDF sample contains an intentional error that is designed to +//! demonstrate the capabilities and features of Driver Verifier and the Device +//! Fundamental tests. +//! +//! The driver is designed to allocate memory using ExAllocatePool2 to its +//! Device Context buffer when a device is added by the PnP manager. However, +//! this buffer is not freed anywhere in the driver, including the driver unload +//! function. +//! +//! By enabling Driver Verifier on this driver, the pool leak +//! violation can be caught when the driver is unloaded and with an active KDNET +//! session, the bug can be analyzed further. + +#![no_std] +#![cfg_attr(feature = "nightly", feature(hint_must_use))] +#![deny(clippy::all)] +#![warn(clippy::pedantic)] +#![warn(clippy::nursery)] +#![warn(clippy::cargo)] +#![allow(clippy::missing_safety_doc)] +#![allow(clippy::doc_markdown)] + +#[cfg(not(test))] +extern crate wdk_panic; + +#[cfg(not(test))] +use wdk_alloc::WDKAllocator; + +#[cfg(not(test))] +#[global_allocator] +static GLOBAL_ALLOCATOR: WDKAllocator = WDKAllocator; + +use wdk_sys::{GUID, PVOID}; + +// {A1B2C3D4-E5F6-7890-1234-56789ABCDEF0} +const GUID_DEVINTERFACE: GUID = GUID { + Data1: 0xA1B2_C3D4u32, + Data2: 0xE5F6u16, + Data3: 0x7890u16, + Data4: [ + 0x12u8, 0x34u8, 0x56u8, 0x78u8, 0x9Au8, 0xBCu8, 0xDEu8, 0xF0u8, + ], +}; + +// Global Buffer for the driver +static mut GLOBAL_BUFFER: PVOID = core::ptr::null_mut(); + +mod driver;