Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions windows_kext/driver/src/ale_callouts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ fn ale_layer_auth(mut data: CalloutData, ale_data: AleLayerData) {
Verdict::PermanentBlock | Verdict::Undeterminable | Verdict::Failed => {
// Packet layer will not see this connection.
crate::dbg!("permanent block {}", key);
data.action_block();
data.action_block_hard();
}
Verdict::PermanentDrop => {
// Packet layer will not see this connection.
Expand All @@ -203,7 +203,7 @@ fn ale_layer_auth(mut data: CalloutData, ale_data: AleLayerData) {
data.action_permit();
} else {
// packet layer will still see the packets.
data.action_block();
data.action_block_hard();
}
}
Verdict::Drop => {
Expand Down
50 changes: 34 additions & 16 deletions windows_kext/driver/src/entry.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::common::ControlCode;
use crate::device;
use alloc::boxed::Box;
use core::sync::atomic::{AtomicPtr, Ordering};
use num_traits::FromPrimitive;
use wdk::irp_helpers::{DeviceControlRequest, ReadRequest, WriteRequest};
use wdk::{err, info, interface};
Expand All @@ -9,9 +10,18 @@ use windows_sys::Win32::Foundation::{NTSTATUS, STATUS_SUCCESS};

static VERSION: [u8; 4] = include!("../../kextinterface/version.txt");

static mut DEVICE: *mut device::Device = core::ptr::null_mut();
/// Global device pointer.
///
/// We use `AtomicPtr` to ensure thread safety.
/// - **Safety**: Prevents data races and acts as a compiler barrier against dangerous optimizations
/// (e.g., load hoisting), ensuring concurrent callouts see a valid, up-to-date pointer.
/// - **Performance**: Negligible overhead. On x64, `Acquire` is free (same as a normal load).
/// On ARM64, it uses efficient hardware-supported load-acquire instructions.
static DEVICE: AtomicPtr<device::Device> = AtomicPtr::new(core::ptr::null_mut());

pub fn get_device() -> Option<&'static mut device::Device> {
return unsafe { DEVICE.as_mut() };
// Acquire pairs with the Release store in driver_entry and the AcqRel swap in driver_unload.
unsafe { DEVICE.load(Ordering::Acquire).as_mut() }
}

// DriverEntry is the entry point of the driver (main function). Will be called when driver is loaded.
Expand Down Expand Up @@ -44,27 +54,35 @@ pub extern "system" fn driver_entry(
driver.set_device_control_fn(Some(device_control));

// Initialize device.
unsafe {
let device = match device::Device::new(&driver) {
Ok(device) => Box::new(device),
Err(err) => {
wdk::err!("filed to initialize device: {}", err);
return -1;
}
};
DEVICE = Box::into_raw(device);
}
let device = match device::Device::new(&driver) {
Ok(device) => Box::new(device),
Err(err) => {
wdk::err!("filed to initialize device: {}", err);
return -1;
}
};
// Release: makes the fully-constructed Device visible to all cores that subsequently
// perform an Acquire load.
DEVICE.store(Box::into_raw(device), Ordering::Release);

STATUS_SUCCESS
}

// driver_unload function is called when service delete is called from user-space.
unsafe extern "system" fn driver_unload(_object: *const DRIVER_OBJECT) {
info!("Unloading complete");
unsafe {
if !DEVICE.is_null() {
_ = Box::from_raw(DEVICE);
}
// Atomically null the pointer before freeing. Any core that performs an Acquire load
// *after* this swap will see null and bail out safely. Any core that already loaded a
// non-null pointer before this swap is protected by the OS-level serialisation:
// - WFP callouts: FilterEngine::drop() (field declared first in Device) calls the WFP
// unregister APIs which block until every in-flight classify callback has returned,
// so no callout thread holds a live reference by the time the memory is freed.
// - IRP dispatch (read/write/ioctl): the I/O Manager guarantees no dispatch routine
// is executing when driver_unload is called.
// The swap is executed exactly once, on the unload path.
let ptr = DEVICE.swap(core::ptr::null_mut(), Ordering::AcqRel);
if !ptr.is_null() {
unsafe { drop(Box::from_raw(ptr)); }
}
}

Expand Down
2 changes: 1 addition & 1 deletion windows_kext/driver/src/packet_callouts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ fn ip_packet_layer(
}
Verdict::PermanentBlock => {
send_request_to_portmaster = false;
data.action_block();
data.action_block_hard();
}
Verdict::Undeterminable | Verdict::PermanentDrop | Verdict::Failed => {
send_request_to_portmaster = false;
Expand Down
215 changes: 215 additions & 0 deletions windows_kext/test/BUILD_DEBUG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
# Building and Running Driver with Debug Logging

## Driver Signing Requirement

Windows requires **all kernel drivers to be signed**. Test signing provides a free alternative to expensive production code signing certificates for development and testing purposes.

## Important: Debug Builds Are Disabled

⚠️ **The driver cannot be compiled in debug mode.** The code contains a compile-time check (`compile_error!`) that prevents debug builds due to potential optimization-related issues and inconsistent compiler behavior between debug and release modes.

However, you can still enable verbose logging in release builds by changing the log level.

## Prerequisites

Already documented in [main README](../README.md), but quick recap:

1. **Visual Studio 2022** with C++ and Windows SDK
2. **Windows Driver Kit (WDK)** installed
3. **Rust toolchain** installed
4. **Test signing enabled** (see below)

## Step 1: Enable Test Signing (One-time Setup)

⚠️ **SECURITY WARNING**: Test signing reduces system security by allowing any locally-generated test certificate to load kernel drivers. **Strongly recommended to use a VM or dedicated test machine**. See "Disabling Test Signing" section below to restore security when done testing.

### Create Test Certificate

Open **PowerShell as Administrator**:

```powershell
# Create a self-signed certificate for driver testing
MakeCert -r -pe -ss PrivateCertStore -n "CN=DriverTestCert" DriverTestCert.cer

# Install the certificate to Trusted Root
CertMgr /add DriverTestCert.cer /s /r localMachine root

# Install to Trusted Publishers (needed for driver installation)
CertMgr /add DriverTestCert.cer /s /r localMachine trustedpublisher
```

### Enable Test Signing Mode

```powershell
# Enable test signing
Bcdedit.exe -set TESTSIGNING ON

# Restart required!
Restart-Computer
```

After restart, you should see **"Test Mode"** watermark in the corner of your screen.

### Verify Test Signing is Enabled

```powershell
bcdedit /enum | Select-String testsigning
# Should show: testsigning Yes
```

## Step 2: Enable Debug Logging in Driver

To see verbose logs from the driver, edit the log level before building.

**Edit `driver/src/logger.rs`:**

```rust
// Change line 8 from:
pub const LOG_LEVEL: u8 = Severity::Warning as u8;

// To one of:
pub const LOG_LEVEL: u8 = Severity::Debug as u8; // Recommended for testing
// pub const LOG_LEVEL: u8 = Severity::Info as u8; // Less verbose
// pub const LOG_LEVEL: u8 = Severity::Trace as u8; // Most verbose
```

For testing, `Debug` level is recommended.

## Step 3: Build Driver in Release Mode

Navigate to the driver directory:

```powershell
cd D:\Projects\Portmaster\portmaster\windows_kext\driver

# Build in release mode (only mode supported)
cargo build --release --target x86_64-pc-windows-msvc

# Output: driver/target/x86_64-pc-windows-msvc/release/driver.lib
```

**Note:** Debug builds (`cargo build` without `--release`) will fail with a compile error by design.

## Step 4: Link the Driver

Copy the `.lib` file to the root directory:

```powershell
cd D:\Projects\Portmaster\portmaster\windows_kext

Copy-Item driver/target/x86_64-pc-windows-msvc/release/driver.lib . -Force
```

Run the linker script:

```powershell
.\link-dev.ps1
```

This creates `driver.sys` in the current directory.

## Step 5: Sign the Driver

## Step 5: Sign the Driver

```powershell
cd D:\Projects\Portmaster\portmaster\windows_kext

# Sign the driver
SignTool sign /v /s PrivateCertStore /n DriverTestCert driver.sys
```

Verify signature:

```powershell
SignTool verify /v /pa driver.sys
```

You should see: **"Successfully verified: driver.sys"**

## Step 6: View Driver Logs

### Ring Buffer Logs (Recommended)

These logs come through the `GetLogs` command.

### Kernel Debugger Output (Not Available in Release)

The `wdk::dbg!()`, `wdk::info!()`, and `wdk::err!()` macros only work in debug builds, which are disabled for this driver. These would output to tools like DebugView via `DbgPrint`, but since debug builds are not allowed, this logging path is not available.

**Use the ring buffer logs** (captured by `dbg!`, `info!`, `warn!`, `err!` macros) for all debugging.

## Common Issues

### "The hash for the file is not present in the specified catalog file"

**Solution**: Your driver isn't signed or the certificate isn't trusted.
```powershell
# Re-sign the driver
SignTool sign /v /s PrivateCertStore /n DriverTestCert driver.sys
```

### "Windows cannot verify the digital signature"

**Solution**: Test signing not enabled or certificate not in Trusted Root.
```powershell
# Check test signing
bcdedit /enum | Select-String testsigning

# Reinstall certificate if needed
CertMgr /add DriverTestCert.cer /s /r localMachine root
```

### "Service marked for deletion"

**Solution**: Manually clean up:
```powershell
sc stop PortmasterKext
sc delete PortmasterKext
# Wait a few seconds
# Then try starting again
```

### "Access is denied" when creating service

**Solution**: Run as Administrator.

### No debug output (`GetLogs` command)

**Solution**:
1. Make sure you edited `driver/src/logger.rs` to set `LOG_LEVEL = Severity::Debug`
2. Rebuild the driver in **release mode** (`cargo build --release`)
3. The driver must be actively running and processing connections to generate logs
4. Default log level (`Warning`) only shows errors, not normal operations

## Quick Build & Test Cycle

```powershell
# 1. (Optional) Enable debug logging - edit driver/src/logger.rs first

# 2. Build driver in release mode
cd D:\Projects\Portmaster\portmaster\windows_kext\driver
cargo build --release

# 3. Link and sign
cd ..
Copy-Item driver/target/x86_64-pc-windows-msvc/release/driver.lib . -Force
.\link-dev.ps1
SignTool sign /v /s PrivateCertStore /n DriverTestCert driver.sys

# 4. Test (in playground, as Administrator)
```

## Disabling Test Signing (When Done Testing)

⚠️ **IMPORTANT**: When finished testing, disable test signing to restore system security.

```powershell
# Run as Administrator
Bcdedit.exe -set TESTSIGNING OFF

# Restart required for changes to take effect
Restart-Computer
```

After restart, the "Test Mode" watermark will disappear and the system will no longer accept test-signed drivers. This restores normal kernel driver security enforcement.Production vs Test Signing
13 changes: 13 additions & 0 deletions windows_kext/test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Test Directory

> ⚠️ **Notice**: This folder and its contents were primarily generated with the assistance of AI and may contain errors or inaccuracies. They are intended solely for local testing and development and must not be used in production.

## Contents

- `build_test.ps1` - Script to build the test-signed driver
- `_out/` - Output directory for built test driver
- `_testcert/` - Test certificates for driver signing

## Purpose

This directory contains tools and utilities for testing the Portmaster Windows kernel driver during development. These are developer tools only and are not part of the production build or release process.
Loading
Loading