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}