IRQL - Compile-Time IRQL Safety for Windows Kernel Drivers
A Rust library providing compile-time verification of IRQL (Interrupt Request Level) constraints for Windows kernel-mode drivers. IRQL violations are caught at compile time, preventing a common class of bugs before code execution.
Features
- Compile-Time Safety: IRQL violations are caught by the Rust compiler, not at runtime
- Zero Runtime Overhead: All checks happen at compile time using Rust's type system
- Clear Error Messages: Helpful diagnostics explain exactly what IRQL violation occurred
- Ergonomic API: Natural Rust syntax with attributes and macros
- no_std Compatible: Designed for kernel-mode environments
Installation
Add this to your Cargo.toml:
[]
= "0.1"
Quick Start
use ;
// Function requiring Dispatch IRQL or higher
// Function requiring Passive IRQL or higher
// Entry point at Passive IRQL
Motivation
In Windows kernel programming, every piece of code runs at a specific IRQL (Interrupt Request Level). Many kernel APIs are only valid at certain IRQLs, and calling them at the wrong IRQL can cause:
- System crashes (BSOD)
- Data corruption
- Deadlocks
- Undefined behavior
Traditional approaches rely on:
- Runtime checks that discover errors too late
- Developer discipline which is error-prone
- Code reviews which are time-consuming and can miss subtle bugs
This library uses Rust's type system to encode IRQL constraints, ensuring that:
- IRQL can only be raised or stay the same, never lowered
- Functions requiring specific IRQLs can only be called from compatible contexts
- Violations are caught at compile time with clear error messages
Concepts
IRQL Hierarchy
From lowest to highest:
| IRQL | Level | Description |
|---|---|---|
| 0 | Passive |
Normal kernel-mode execution, can access paged memory |
| 1 | Apc |
Asynchronous Procedure Call level |
| 2 | Dispatch |
Dispatcher/DPC level, most common for driver code |
| 3-26 | Dirql |
Device interrupt levels |
| 27 | Profile |
Profiling interrupt |
| 28 | Clock |
Clock interrupt |
| 29 | Ipi |
Inter-processor interrupt |
| 30 | Power |
Power failure |
| 31 | High |
Machine check and catastrophic errors |
The Golden Rule
IRQL can only stay the same or be raised, never lowered.
This library enforces this rule at compile time through the IrqlCanRaiseTo trait.
API Reference
Attributes
#[requires_irql(Level)]
Marks a function or impl block as requiring a minimum IRQL level.
#[root_irql(Level)]
Marks an entry point function with a specific IRQL context.
Macros
call_irql!()
Calls IRQL-constrained functions with appropriate type parameters.
Examples
Basic Functions
use ;
Structs and Methods
use ;
Compile-Time Error Prevention
use ;
Error message:
error: IRQL violation: cannot call function at `Passive` IRQL from current IRQL level `Dispatch`
--> src/main.rs:10:5
|
10 | call_irql!(low_level());
| ^^^^^^^^^^^^^^^^^^^^^^^ cannot lower IRQL or call incompatible IRQL level
|
= note: IRQL can only stay the same or be raised, never lowered
How It Works
Type-Level IRQL Encoding
Each IRQL level is represented as a zero-sized type with no runtime cost:
; // No runtime cost
; // Just a compile-time marker
Trait-Based Constraints
The IrqlCanRaiseTo trait encodes the IRQL hierarchy:
// Passive can raise to Dispatch
// Dispatch CANNOT raise to Passive (no such impl exists)
Generic Parameters
Functions annotated with #[requires_irql] gain a generic IRQL parameter:
// Before macro:
// After macro expansion:
Compile-Time Verification
The Rust compiler verifies the constraints:
// Compiles: Passive -> Dispatch (raising IRQL)
;
// Compile error: Dispatch -> Passive (lowering IRQL)
; // Error!
Safety Considerations
Guarantees
- Functions are only called from compatible IRQL contexts
- IRQL is never lowered (only raised or maintained)
- Zero runtime overhead
Limitations
This library provides compile-time verification only. Developers must ensure:
- Runtime IRQL actually matches compile-time annotations
- Entry points are annotated with their actual runtime IRQL
- IRQL-raising operations (spinlocks, etc.) are properly tracked
Best Practices
- Annotate entry points correctly: Use
#[root_irql]with the actual runtime IRQL - Be conservative: When in doubt, use a lower IRQL requirement
- Test thoroughly: Compile-time checks complement but don't replace testing
- Document IRQL assumptions: Help future maintainers understand your code