Skip to content

fix(windows-kext): fix potential BSODs and block verdict bypass in WFP callouts#2136

Merged
stenya merged 9 commits intodevelopmentfrom
fix/windows-kext-bsod
Mar 10, 2026
Merged

fix(windows-kext): fix potential BSODs and block verdict bypass in WFP callouts#2136
stenya merged 9 commits intodevelopmentfrom
fix/windows-kext-bsod

Conversation

@stenya
Copy link
Copy Markdown
Contributor

@stenya stenya commented Mar 6, 2026

Summary

This PR addresses several root causes of BSODs in the Windows kernel extension and hardens the WFP callout logic.

Changes

BSOD Fixes

  • Heap-allocate WFP transport send params (packet.rs)
    FWPS_TRANSPORT_SEND_PARAMS1 and remote_ip are now stored inside a heap-allocated Box<TransportPacketList>. Previously they lived on the stack and could be freed before FwpsInjectTransportSendAsync1 finished reading them asynchronously.

  • Copy WFP control data before callout returns (packet.rs)
    The control_data pointer passed by WFP is only valid for the duration of the classify callback. The bytes are now copied into an owned Box<[u8]> instead of holding a raw pointer that would dangle after the callout returned.

  • Replace global device pointer with AtomicPtr (entry.rs)
    The static mut DEVICE raw pointer is replaced with AtomicPtr<Device> with proper Acquire/Release ordering, preventing data races between concurrent callout threads and the load/unload path.

WFP Verdict Handling

  • Prevent downstream filters from overriding block verdicts (callout_data.rs, ale_callouts.rs, packet_callouts.rs)
    Replaced action_block() with action_block_hard() which additionally clears the FWPS_RIGHT_ACTION_WRITE flag, ensuring that lower-priority WFP filters in the chain cannot override a block decision.

  • Ignore STATUS_FWP_TXN_IN_PROGRESS in reset_all_filters (callout_data.rs)
    When another WFP transaction is already in progress, reset_all_filters returns this status. It is now treated as a no-op: the concurrent transaction will trigger the same reauthorization, and the verdict is already in the connection cache.

Code Quality

  • Fix advance() to take &mut self instead of &self
  • Remove unused spin_lock module
  • Add lifetime elision fixes to suppress compiler warnings
  • Add BUILD_DEBUG.md and build_test.ps1 for local test-signed driver builds

Testing

Tested locally with a test-signed release build. See test/BUILD_DEBUG.md for setup instructions.

Summary by CodeRabbit

Release Notes

  • New Features

    • Implemented hard blocking mechanism to prevent subsequent filters from overriding block actions.
  • Documentation

    • Added comprehensive debug build instructions and test procedure guide.
  • Chores

    • Added automated PowerShell script for test driver building and signing.
    • Internal code refactoring for improved thread safety and API consistency.

… params

FwpsInjectTransportSendAsync1 dereferences FWPS_TRANSPORT_SEND_PARAMS1
(and remote_address within it) asynchronously after the callsite returns.
The params were stack-allocated, so WFP may have accessed freed stack memory
at DISPATCH_LEVEL, potentially causing PAGE_FAULT_IN_NONPAGED_AREA or
DRIVER_IRQL_NOT_LESS_OR_EQUAL BSODs.

Candidate fix: embed send_params in TransportPacketList, box the whole struct
before calling the inject API, and populate send_params (remote_address points
into boxed remote_ip) only after boxing so all pointers are into stable
non-paged heap memory. Introduce free_transport_packet as the WFP completion
callback that drops Box<TransportPacketList> once injection is complete.

safing/portmaster-shadow#38
…llout returns

FwpsInjectTransportSendAsync1 reads control_data (WSACMSGHDR) asynchronously
after the ALE classify callout returns. The pointer was into WFP-managed
metadata memory that WFP frees immediately when the callout returns, leaving
a dangling pointer used during deferred injection. This could
cause PAGE_FAULT_IN_NONPAGED_AREA or DRIVER_IRQL_NOT_LESS_OR_EQUAL BSODs.

Candidate fix: change TransportPacketList.control_data from Option<NonNull<[u8]>>
to Option<Box<[u8]>> and copy the bytes inside from_ale_callout while the WFP
pointer is still valid. The owned copy lives on the heap as part of the boxed
TransportPacketList context and is freed by free_transport_packet after WFP
calls the completion callback.

safing/portmaster-shadow#38
When completing a Reauthorization classify defer, reset_all_filters()
would fail with STATUS_FWP_TXN_IN_PROGRESS if another WFP transaction
was already running (e.g. from a concurrent ClearCache command). This
caused the packet to be silently dropped instead of injected.

This error is safe to ignore: the concurrent transaction will trigger
WFP reauthorization for all connections anyway, and the verdict for the
current connection is already written to the connection_cache before
complete() is called, so the callout will apply the correct verdict when
the injected packet passes through.

All other errors from reset_all_filters() are still propagated.
… WFP filters

Rename action_block() to action_block_hard(), which additionally calls
clear_write_flag(). This prevents lower-weight filters in the same WFP
sublayer from overwriting a block action set by Portmaster's callout.

Updated call sites:
- ALE layer: PermanentBlock, Undeterminable, Failed, and inbound Block
- Packet layer: PermanentBlock
@stenya stenya self-assigned this Mar 6, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 6, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 269da5c0-d95b-4fe3-8b8b-f897ff99191d

📥 Commits

Reviewing files that changed from the base of the PR and between d216fc2 and 35ecee8.

📒 Files selected for processing (14)
  • windows_kext/driver/src/ale_callouts.rs
  • windows_kext/driver/src/entry.rs
  • windows_kext/driver/src/packet_callouts.rs
  • windows_kext/test/BUILD_DEBUG.md
  • windows_kext/test/README.md
  • windows_kext/test/build_test.ps1
  • windows_kext/wdk/src/ffi.rs
  • windows_kext/wdk/src/filter_engine/callout_data.rs
  • windows_kext/wdk/src/filter_engine/net_buffer.rs
  • windows_kext/wdk/src/filter_engine/packet.rs
  • windows_kext/wdk/src/filter_engine/transaction.rs
  • windows_kext/wdk/src/irp_helpers.rs
  • windows_kext/wdk/src/lib.rs
  • windows_kext/wdk/src/rw_spin_lock.rs

📝 Walkthrough

Walkthrough

This PR introduces thread-safe device access via AtomicPtr, refactors memory management in transport packet injection, changes block behavior to enforce immutable verdicts, updates API signatures with explicit lifetimes, adds documentation and tooling for debug driver builds, and removes a public module export.

Changes

Cohort / File(s) Summary
Blocking Behavior Changes
windows_kext/driver/src/ale_callouts.rs, windows_kext/driver/src/packet_callouts.rs, windows_kext/wdk/src/filter_engine/callout_data.rs
Replaces action_block() with action_block_hard() to enforce hard block verdicts that prevent subsequent filters from overriding the action; also adds logic to clear the write flag in action_block_hard() implementation.
Thread-Safe Device Management
windows_kext/driver/src/entry.rs
Converts static device pointer from raw *mut device::Device to AtomicPtr<device::Device> with Acquire/Release semantics for get_device() and driver entry/unload operations.
Transport Packet Memory Refactor
windows_kext/wdk/src/filter_engine/packet.rs
Replaces raw pointer wrapper for control data with owned Box<[u8]>, introduces send_params field to TransportPacketList, boxes entire packet structure for heap stability, and adds new free_transport_packet completion callback.
Transaction In-Progress Handling
windows_kext/wdk/src/filter_engine/callout_data.rs (guarded match), windows_kext/wdk/src/filter_engine/transaction.rs
Adds guarded handling in ClassifyDefer::Reauthorization to ignore STATUS_FWP_TXN_IN_PROGRESS errors; documents this status in begin_write().
API Signature Refinements
windows_kext/wdk/src/irp_helpers.rs, windows_kext/wdk/src/rw_spin_lock.rs, windows_kext/wdk/src/filter_engine/net_buffer.rs
Adds explicit lifetime parameters to ReadRequest, WriteRequest, DeviceControlRequest constructors and RwLockGuard return types; changes NetBufferList::advance() from &self to &mut self.
Build and Tooling
windows_kext/test/build_test.ps1, windows_kext/test/BUILD_DEBUG.md, windows_kext/test/README.md
Adds PowerShell build script for test driver signing and linking, comprehensive debug build documentation, and test directory README.
Crate Configuration and Module Changes
windows_kext/wdk/src/ffi.rs, windows_kext/wdk/src/lib.rs
Adds crate-level allow(non_snake_case) attribute; removes public spin_lock module export.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • dhaavi
✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/windows-kext-bsod

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@stenya stenya changed the title windows-kext: fix potential BSODs and block verdict bypass in WFP callouts fix(windows-kext): fix potential BSODs and block verdict bypass in WFP callouts Mar 6, 2026
@stenya stenya requested a review from vlabo March 6, 2026 19:04
@stenya stenya added this to the v2.1.9 milestone Mar 6, 2026
@stenya stenya marked this pull request as ready for review March 10, 2026 12:18
@stenya stenya merged commit 3b0b174 into development Mar 10, 2026
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant