win_lookaside/
lib.rs

1//! This crate provides an experimental Rust allocator for the Windows Kernel based on [Lookaside
2//! Lists](https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/using-lookaside-lists).
3//!
4//! Given the nature of Lookaside Lists (fixed-size buffers) this Allocator is not meant to be used
5//! as the Global Allocator for this reason this crate does not implement the GlobalAlloc trait,
6//! on the other hand it does implement the Allocator trait (no grow nor shrink) so it can
7//! be used as the allocator in `xxx_in` methods.
8//! > The default implementation of grow/grow_zeroed & shrink of the Allocator API has been
9//! overridden to throw a panic. This is done just to make the user aware that calling these
10//! methods on this allocator is a misuse of the Lookaside API.
11//!
12//! Obviously, this crate requires the `allocator_api`feature is enabled (hence this being an
13//! experimental/unstable crate).
14//!
15//! Alternatively, this can be used directly by initializing the allocator and using allocate &
16//! deallocate methods to get an entry from the list as a  `*mut u8` and return an entry to the
17//! list respectively.
18//!
19//! Since this is meant to be used in the kernel, this Allocator can return `NULL` and doesn't
20//! trigger the `alloc_error_handler` if an OOM condition happens. This requires using
21//! fallible APIs such as [Box::try_new_in](https://doc.rust-lang.org/nightly/alloc/boxed/struct.Box.html#method.try_new_in)
22//! or crates such as [fallible_vec](https://docs.rs/fallible_vec/latest/fallible_vec/index.html).
23//!
24//! # Usage
25//!
26//! If working on a Driver fully written in Rust, the following example shows how we can make use
27//! of the Allocator.
28//!
29//! All in one function just for the sake of the example, usually we would store the Lookaside
30//! Allocator in some structure or global variable initialized in the DriverEntry and destroy it
31//! in the DriverUnload.
32//! ```
33//! #![no_std]
34//! #![feature(allocator_api)]
35//! #[macro_use] extern crate win_lookaside;
36//!
37//! extern crate alloc;
38//!
39//! use alloc::boxed::Box;
40//! use win_lookaside::LookasideAlloc;
41//! use windows_sys::Wdk::Foundation::NonPagedPool;
42//!
43//! fn example() {
44//!     // Init Lookaside List allocator with default values//!
45//!     let mut allocator = LookasideAlloc::default();
46//!
47//!     // Init Lookaside List with fixed-size to hold a u32
48//!     // Properly handle possible InitError;
49//!     allocator.init(core::mem::size_of::<u32>(), NonPagedPool as i32, None, None, None).unwrap();
50//!
51//!     // Allocate from Lookaside & Free to it on Drop
52//!     {
53//!         let Ok(ctx) = Box::try_new_in(10, &allocator) else {
54//!             return; // AllocError
55//!         };
56//!     }
57//!
58//!     // Destroy Lookaside List Allocator
59//!     allocator.destroy();
60//! }
61//! ```
62//!
63//!
64//! Another option is if we are working with a Driver written in C++ and we want to work on a
65//! extensions/component in Rust. We can write a thin FFI layer on top of this crate to expose the
66//! functionality to the Driver.
67//!
68//! A very simple implementation of how this FFI layer could look like is the following:
69//! ```
70//! #![no_std]
71//! #![feature(allocator_api)]
72//! #[macro_use] extern crate win_lookaside;
73//!
74//! extern crate alloc;
75//!
76//! use alloc::boxed::Box;
77//! use windows_sys::Wdk::Foundation::PagedPool;
78//! use windows_sys::Win32::Foundation::{NTSTATUS, STATUS_INSUFFICIENT_RESOURCES, STATUS_SUCCESS};
79//! use win_lookaside::LookasideAlloc;
80//!
81//! // Interior mutability due to the way the Lookaside API works
82//! static mut LOOKASIDE: LookasideAlloc = LookasideAlloc::default();
83//!
84//! struct Context{};
85//!
86//! #[no_mangle]
87//! pub unsafe extern "C" fn init_lookaside(tag: u32) -> NTSTATUS {
88//!     LOOKASIDE.init(core::mem::size_of::<Context>(), PagedPool, Some(tag), None, None )?;
89//!     STATUS_SUCCESS
90//! }
91//!
92//! #[no_mangle]
93//! pub extern "C" fn create_context(context: *mut *mut Context) -> FfiResult<()> {
94//!     let Ok(ctx) = unsafe { Box::try_new_in(Context {}, &LOOKASIDE) } else {
95//!         return STATUS_INSUFFICIENT_RESOURCES;
96//!     };
97//!
98//!     unsafe {
99//!         *context = Box::into_raw(ctx);
100//!     }
101//!
102//!     STATUS_SUCCESS
103//! }
104//!
105//! #[no_mangle]
106//! pub extern "C" fn remove_context(context: *mut Context) {
107//!     let _ctx = unsafe { Box::from_raw_in(context, &LOOKASIDE) };
108//! }
109//!
110//! #[no_mangle]
111//! pub unsafe extern "C" fn free_lookaside() {
112//!     LOOKASIDE.destroy();
113//! }
114//! ```
115//! > Here the Context is just an empty struct, but it could be something more complex that could
116//! offer more functionality and the C++ driver would just need to store those as an opaque pointer.
117//!
118//! # Remarks
119//! This crate has been developed under the 22H2 WDK meaning certain Lookaside API methods are
120//! exported instead of inlined. The crate is yet to be tested in an older WDK, behavior when
121//! trying to build might be different.
122//!
123//! At the moment the crate uses [spin](https://crates.io/crates/spin) as the synchronization
124//! mechanism. Even thou this does the job, ideally at some point it should use synchronization
125//! primitives native to the OS.
126//!
127//!
128#![no_std]
129#![feature(allocator_api)]
130use core::{
131    alloc::{AllocError, Allocator, Layout},
132    cell::RefCell,
133    ffi::c_void,
134    ptr::NonNull,
135    sync::atomic::{AtomicBool, Ordering},
136};
137
138/*
139TODO: Review WDK metadata https://github.com/microsoft/wdkmetadata#overview
140TODO: Use bindings from windows-rs when available
141use windows_sys::{
142    Wdk::Foundation::POOL_TYPE,
143    Win32::Foundation::{NTSTATUS, STATUS_SUCCESS},
144};
145
146use windows_sys::Wdk::System::SystemServices::{
147    ExAllocateFromLookasideListEx,
148    ExDeleteLookasideListEx,
149    ExFlushLookasideListEx,
150    ExFreeToLookasideListEx,
151    ExInitializeLookasideListEx,
152};
153*/
154
155/// Default PoolTag used by the Lookaside Allocator if none passed when initializing it
156pub const DEFAULT_POOL_TAG: u32 = u32::from_ne_bytes(*b"srLL");
157
158/// Possible Errors returned by the Lookaside List Allocator
159pub enum LookasideError {
160    InitError(NtStatus),
161}
162
163/// Lookaside List Allocator Result
164pub type LookasideResult<T> = Result<T, LookasideError>;
165
166/// The LookasideListAllocateEx routine allocates the storage for a new lookaside-list entry when
167/// a client requests an entry from a lookaside list that is empty.
168///
169/// More info: <https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nc-wdm-allocate_function_ex>
170pub type AllocateFunctionEx = Option<
171    unsafe extern "system" fn(
172        pooltype: i32, // Change to POOL_TYPE
173        numberofbytes: usize,
174        tag: u32,
175        lookaside: *mut LookasideList,
176    ) -> *mut c_void,
177>;
178/// The LookasideListFreeEx routine frees the storage for a lookaside-list entry when a client
179/// tries to insert the entry into a lookaside list that is full.
180///
181/// More info: <https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nc-wdm-free_function_ex>
182pub type FreeFunctionEx =
183    Option<unsafe extern "system" fn(buffer: *const c_void, *mut LookasideList)>;
184
185// Newtype over windows-sys [NTSTATUS (i32)](https://docs.rs/windows-sys/0.48.0/windows_sys/Win32/Foundation/type.NTSTATUS.html)
186// Change to windows-sys NTSTATUS when windows-sys bindings are ready
187/// Newtype over i32.
188#[repr(transparent)]
189#[derive(Copy, Clone)]
190pub struct NtStatus(i32);
191
192impl NtStatus {
193    /// True if NTSTATUS == STATUS_SUCCESS
194    pub fn is_ok(&self) -> bool {
195        self.0 == 0
196    }
197
198    /// True if NTSTATUS != STATUS_SUCCESS
199    pub fn is_err(&self) -> bool {
200        self.0 != 0
201    }
202}
203
204/// Wrapper over the [_LOOKASIDE_LIST_EX](https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/eprocess#lookaside_list_ex)
205/// type
206///
207/// See: <https://www.vergiliusproject.com/kernels/x64/Windows%2011/22H2%20(2022%20Update)/_LOOKASIDE_LIST_EX>
208#[repr(C)]
209pub struct LookasideList {
210    general_lookaside_pool: [u8; 0x60],
211}
212
213impl LookasideList {
214    const fn init() -> Self {
215        LookasideList {
216            general_lookaside_pool: [0; 0x60],
217        }
218    }
219
220    fn as_ptr(&self) -> *const Self {
221        self as *const _
222    }
223
224    fn as_mut_ptr(&mut self) -> *mut Self {
225        self as *mut _
226    }
227}
228
229#[link(name = "ntoskrnl")]
230extern "system" {
231    /// <https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-exinitializelookasidelistex>
232    pub fn ExInitializeLookasideListEx(
233        lookaside: *mut LookasideList,
234        allocate: AllocateFunctionEx,
235        free: FreeFunctionEx,
236        pool_type: i32, // Change to POOL_TYPE
237        flags: u32,
238        size: usize,
239        tag: u32,
240        depth: u16,
241    ) -> NtStatus;
242
243    /// <https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-exdeletelookasidelistex>
244    pub fn ExDeleteLookasideListEx(lookaside: *mut LookasideList);
245    /// <https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-exallocatefromlookasidelistex>
246    pub fn ExAllocateFromLookasideListEx(lookaside: *mut LookasideList) -> *mut u64;
247    /// <https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-exfreetolookasidelistex>
248    pub fn ExFreeToLookasideListEx(lookaside: *mut LookasideList, entry: u64);
249}
250
251/// Lookaside List Allocator
252pub struct LookasideAlloc {
253    init: AtomicBool,
254    lookaside: spin::Mutex<RefCell<LookasideList>>,
255}
256
257impl Default for LookasideAlloc {
258    fn default() -> Self {
259        Self::default()
260    }
261}
262
263impl Drop for LookasideAlloc {
264    fn drop(&mut self) {
265        self.destroy();
266    }
267}
268
269impl LookasideAlloc {
270    #[inline(always)]
271    fn borrow_mut_list(list: &RefCell<LookasideList>) -> *mut LookasideList {
272        // Should be called with the lock so it's safe to assume the value is not currently
273        // borrowed hence the unwrap.
274        list.try_borrow_mut().unwrap().as_mut_ptr()
275    }
276
277    /// const default initializer for the Lookaside List allocator.
278    pub const fn default() -> Self {
279        LookasideAlloc {
280            init: AtomicBool::new(false),
281            lookaside: spin::Mutex::new(RefCell::new(LookasideList::init())),
282        }
283    }
284
285    /// Initialize the Lookaside list
286    pub fn init(
287        &mut self,
288        size: usize,
289        pool_type: i32, // Change POOL_TYPE
290        tag: Option<u32>,
291        flags: Option<u32>,
292        alloc_fn: AllocateFunctionEx,
293        free_fn: FreeFunctionEx,
294    ) -> LookasideResult<()> {
295        let mut lock = self.lookaside.lock();
296        let status = unsafe {
297            ExInitializeLookasideListEx(
298                lock.get_mut(),
299                alloc_fn,
300                free_fn,
301                pool_type,
302                flags.unwrap_or(0),
303                size,
304                tag.unwrap_or(DEFAULT_POOL_TAG),
305                0,
306            )
307        };
308
309        if status.is_err() {
310            return Err(LookasideError::InitError(status));
311        }
312
313        self.init.store(true, Ordering::SeqCst);
314        Ok(())
315    }
316
317    /// Delete the Lookaside List
318    pub fn destroy(&mut self) {
319        let mut lock = self.lookaside.lock();
320        if self.init.load(Ordering::Relaxed) {
321            unsafe {
322                ExDeleteLookasideListEx(lock.get_mut());
323            }
324        }
325    }
326
327    // Lookaside API guarantees thread-safety when calling Alloc & Free
328    /// Allocate from Lookaside List
329    pub fn alloc(&self) -> *mut u8 {
330        let lock = self.lookaside.lock();
331        unsafe { ExAllocateFromLookasideListEx(Self::borrow_mut_list(&lock)) as _ }
332    }
333
334    /// Free to Lookaside List
335    pub fn free(&self, ptr: *mut u8) {
336        let lock = self.lookaside.lock();
337        unsafe { ExFreeToLookasideListEx(Self::borrow_mut_list(&lock), ptr as _) }
338    }
339}
340
341unsafe impl Allocator for LookasideAlloc {
342    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
343        let pool = self.alloc();
344
345        if pool.is_null() {
346            return Err(AllocError);
347        }
348
349        let slice = unsafe { core::slice::from_raw_parts_mut(pool, layout.size()) };
350        Ok(unsafe { NonNull::new_unchecked(slice) })
351    }
352
353    unsafe fn deallocate(&self, ptr: NonNull<u8>, _layout: Layout) {
354        self.free(ptr.as_ptr())
355    }
356
357    /// Lookaside List does not support Grow
358    unsafe fn grow(
359        &self,
360        _ptr: NonNull<u8>,
361        _old_layout: Layout,
362        _new_layout: Layout,
363    ) -> Result<NonNull<[u8]>, AllocError> {
364        panic!("Not supported");
365    }
366
367    /// Lookaside List does not support Grow
368    unsafe fn grow_zeroed(
369        &self,
370        _ptr: NonNull<u8>,
371        _old_layout: Layout,
372        _new_layout: Layout,
373    ) -> Result<NonNull<[u8]>, AllocError> {
374        panic!("Not supported");
375    }
376
377    /// Lookaside List does not support Shrink
378    unsafe fn shrink(
379        &self,
380        _ptr: NonNull<u8>,
381        _old_layout: Layout,
382        _new_layout: Layout,
383    ) -> Result<NonNull<[u8]>, AllocError> {
384        panic!("Not supported");
385    }
386}