irql - Compile-time IRQL safety for Windows kernel drivers in Rust

53 points naorhaziz 7 comments

I've been writing Windows kernel drivers in Rust and kept running into the same problem: IRQL violations. Call a paged function at DISPATCH_LEVEL? Blue screen. Drop paged-pool memory at elevated IRQL? Blue screen. These bugs hide in deep call chains and only surface under the right timing.

The traditional defense is PAGED_CODE() -- a runtime assert. It only catches bugs you actually hit during testing.

irql encodes the entire IRQL hierarchy into Rust's type system. Violations become compiler errors:

#[irql(max = Dispatch)]
fn acquire_spinlock() { /* ... */ }

#[irql(max = Passive)]
fn driver_routine() {
    call_irql!(acquire_spinlock()); // OK: Passive can raise to Dispatch
}

Wrong transition? Compiler says no:

error[E0277]: IRQL violation: cannot reach `Passive` from `Dispatch`
              -- would require lowering

Zero runtime cost, zero binary overhead -- everything is erased during monomorphization.

How it works: #[irql(max = Dispatch)] adds a hidden IRQL type parameter bounded by IrqlCanRaiseTo<Dispatch>. call_irql! threads it through every call as a turbofish argument. The compiler checks the full chain.

Also includes (optional alloc feature, nightly):

  • IrqlBox and IrqlVec with automatic kernel pool selection (PagedPool vs NonPagedPool)
  • Compile-time drop safety -- paged-pool memory can't be dropped at Dispatch or above, enforced via auto traits
  • All allocations are fallible (Result) -- no OOM panics, no OOM blue screens

Core crate builds on stable. Works on functions, impl blocks, and trait impl blocks.

I wrote a blog post covering the full story and internals: irql: Compile-Time IRQL Safety for Windows Kernel Drivers in Rust

Would love feedback, especially from anyone doing Rust driver development.