Here's a short set of basic guidelines for kernel-mode programming.
Prepare drivers to handle low-memory conditions. It's critically important that Windows drivers do not crash if they cannot allocate memory. If you absolutely must have memory available for certain aspects of your driver, allocate the memory when the driver is initialized and store a pointer to it for later use. Otherwise, gracefully handle the allocation failure by failing the associated operation or arranging to retry it later.
Use tagged memory allocation for easier debugging of memory leaks. Chapter 12, "WDF Support Objects," provides more information about memory allocation.
Do not access any pageable code or data while holding a spin lock. Even routines that normally run at PASSIVE_LEVEL will run at DISPATCH_LEVEL while they are holding a spin lock. Use the PAGED_CODE() macro in your pageable functions to help catch this.
Do not hold spin locks longer than absolutely necessary. If routine A has a spin-locked resource and routine B also tries to acquire the lock, routine B will "spin" in a wait loop until the lock is available. As long as routine B is "spinning," no PASSIVE_LEVEL code can run on its assigned processor.
Be careful about acquiring and releasing spin locks. If a routine holds a spin lock and tries to acquire it again without first releasing the lock, a deadlock occurs.
Release multiple spin locks in the reverse of the order in which they were acquired. For example, a driver routine acquires spin lock A, acquires spin lock B, releases A, and then releases B. This creates an interval in which one thread could own spin lock A but cannot acquire B, while another thread owns spin lock B but cannot acquire A, thus creating a deadlock.
Run Driver Verifier with deadlock detection enabled. The Driver Verifier deadlock detection feature will help you verify and debug your use of spin locks.
Make sure that memory is allocated from the correct pool. Determine whether a memory allocation should be in the paged or nonpaged pool.
Run Driver Verifier with "Force IRQL Checking." This can help you discover many paging-related problems.
Chapter 21, "Tools for Testing WDF Drivers," describes how to use Driver Verifier in your development processes.
Do not trust anything that comes from user mode. Always validate the length of any buffers that you receive, and verify that the data they contain is not corrupted or malicious. For direct I/O or neither I/O, copy any parameters to kernel memory before validating them so that the application cannot change them after validation.
Do not simply dereference a user-mode pointer in a kernel-mode routine. At best, you're likely to get nonsense. At worst, you can unknowingly breach security or crash the system. To dereference a user-mode pointer, you must be running in the correct process context and you must probe and lock the memory with DDI routines that throw an exception if the address does not pass several validity checks. Any attempt to access a user-mode pointer should be wrapped in a structured exception handling block (such as a __try/__except block). This practice catches any memory access exceptions that could occur if the application invalidates the address while you are trying to access it.
Chapter 8, "I/O Flow and Dispatching," describes how to safely access user-mode memory.
Be careful about blocking threads. If your routine runs at an elevated IRQL, it's preventing anything else from running on that processor except a routine that runs at an even higher IRQL.
Chapter 15, "Scheduling, Thread Context, and IRQL," discusses threads in kernel mode.
Run Driver Verifier and KMDF Verifier. Begin running Driver Verifier and KMDF Verifier as soon as you can successfully load the driver. Driver Verifier can help catch errors that can be difficult to detect in normal operation, such as use of paged code or data at IRQL>=DISPATCH_LEVEL.
Run PREfast and SDV on your code as soon as it compiles. These static analysis tools can catch problems in your code early, before errors become difficult and complicated to fix.
Chapter 23, "PREfast for Drivers," describes how to use PREfast in your development process.
Use the VERIFY_IS_IRQL_PASSIVE_LEVEL() macro at the start of all routines that should be running at PASSIVE_LEVEL. The VERIFY_IS_IRQL_PASSIVE_LEVEL() macro can be used by KMDF drivers to verify that a routine is running at passive level. It causes the debugger to prompt the programmer if the IRQL is greater than PASSIVE_LEVEL. If no debugger is attached, VERIFY_IS_IRQL_PASSIVE_LEVEL() throws an exception. The PAGED_CODE() macro is similar to VERIFY_IS_IRQL_PASSIVE_LEVEL() and can be used with any kernel-mode driver. PAGED_CODE() prompts the debugger if the routine is running at DISPATCH_LEVEL or greater.
Chapter 15, "Scheduling, Thread Context, and IRQL," provides information about Windows exceptions.
Use the UNREFERENCED_PARAMETER macro for any parameters that your routine does not use. This macro disables warnings that the compiler will otherwise issue when it encounters an unreferenced parameter. UNREFERENCED_PARAMETER is defined in the standard WDK Ntdef.h header file, which is included by Ntddk.h.