feat(kext): track owner PID of connected user-space process

Add IRP_MJ_CREATE and IRP_MJ_CLEANUP handlers to capture and clear the
PID of the user-space process that holds the device handle open.

- wdk/ffi: expose PsGetCurrentProcessId kernel API
- wdk/irp_helpers: add CreateRequest and CleanupRequest wrappers
- driver/device: add owner_pid AtomicU32 field with lock-free access
  for callouts; add is_owner_pid() helper
- driver/entry: register driver_create/driver_cleanup dispatch routines
  that store/clear owner_pid on connect/disconnect
This commit is contained in:
Alexandr Stelnykovych
2026-03-05 14:54:11 +02:00
parent 78fce7650d
commit 13f85929b2
4 changed files with 99 additions and 1 deletions
+12
View File
@@ -1,4 +1,5 @@
use alloc::string::String;
use core::sync::atomic::{AtomicU32, Ordering};
use num_traits::FromPrimitive;
use protocol::{command::CommandType, info::Info};
use smoltcp::wire::{IpAddress, IpProtocol, Ipv4Address, Ipv6Address};
@@ -34,6 +35,10 @@ pub struct Device {
pub(crate) injector: Injector,
pub(crate) network_allocator: NetworkAllocator,
pub(crate) bandwidth_stats: Bandwidth,
/// PID of the user-space process that currently holds the device handle open.
/// Written once on IRP_MJ_CREATE, cleared on IRP_MJ_CLEANUP.
/// AtomicU32 gives lock-free reads in callouts with zero overhead.
pub(crate) owner_pid: AtomicU32,
}
impl Device {
@@ -57,9 +62,16 @@ impl Device {
injector: Injector::new(),
network_allocator: NetworkAllocator::new(),
bandwidth_stats: Bandwidth::new(),
owner_pid: AtomicU32::new(0),
})
}
/// Returns the PID of the process that currently has the device handle open, or 0 if none.
pub fn is_owner_pid(&self, pid: u32) -> bool {
let p = self.owner_pid.load(Ordering::Acquire);
p != 0 && p == pid
}
/// Cleanup is called just before drop.
// pub fn cleanup(&mut self) {}
+32 -1
View File
@@ -2,7 +2,7 @@ use crate::common::ControlCode;
use crate::device;
use alloc::boxed::Box;
use num_traits::FromPrimitive;
use wdk::irp_helpers::{DeviceControlRequest, ReadRequest, WriteRequest};
use wdk::irp_helpers::{CleanupRequest, CreateRequest, DeviceControlRequest, ReadRequest, WriteRequest};
use wdk::{err, info, interface};
use windows_sys::Wdk::Foundation::{DEVICE_OBJECT, DRIVER_OBJECT, IRP};
use windows_sys::Win32::Foundation::{NTSTATUS, STATUS_SUCCESS};
@@ -39,6 +39,8 @@ pub extern "system" fn driver_entry(
// Set driver functions.
driver.set_driver_unload(Some(driver_unload));
driver.set_create_fn(Some(driver_create));
driver.set_cleanup_fn(Some(driver_cleanup));
driver.set_read_fn(Some(driver_read));
driver.set_write_fn(Some(driver_write));
driver.set_device_control_fn(Some(device_control));
@@ -68,6 +70,35 @@ unsafe extern "system" fn driver_unload(_object: *const DRIVER_OBJECT) {
}
}
/// driver_create is triggered when user-space opens a handle to the device (CreateFile).
unsafe extern "system" fn driver_create(
_device_object: *const DEVICE_OBJECT,
irp: *mut IRP,
) -> NTSTATUS {
let mut create_request = CreateRequest::new(irp.as_mut().unwrap());
if let Some(device) = get_device() {
let pid = create_request.get_requestor_pid();
device.owner_pid.store(pid, core::sync::atomic::Ordering::Release);
info!("Device opened by PID {}", pid);
}
create_request.complete();
create_request.get_status()
}
/// driver_cleanup is triggered when user-space closes the last handle to the device.
unsafe extern "system" fn driver_cleanup(
_device_object: *const DEVICE_OBJECT,
irp: *mut IRP,
) -> NTSTATUS {
let mut cleanup_request = CleanupRequest::new(irp.as_mut().unwrap());
if let Some(device) = get_device() {
let old_pid = device.owner_pid.swap(0, core::sync::atomic::Ordering::Release);
info!("Device closed by PID {}", old_pid);
}
cleanup_request.complete();
cleanup_request.get_status()
}
// driver_read event triggered from user-space on file.Read.
unsafe extern "system" fn driver_read(
_device_object: *const DEVICE_OBJECT,
+4
View File
@@ -534,4 +534,8 @@ extern "C" {
/// The KeQuerySystemTime routine obtains the current system time.
/// System time is a count of 100-nanosecond intervals since January 1, 1601. System time is typically updated approximately every ten milliseconds. This value is computed for the GMT time zone.
pub(crate) fn pm_QuerySystemTime() -> u64;
/// Returns the process identifier of the current process.
/// This is safe to call from IRP_MJ_CREATE handlers, which always execute in the context of the initiating user-space process.
pub(crate) fn PsGetCurrentProcessId() -> HANDLE;
}
+51
View File
@@ -9,6 +9,57 @@ use windows_sys::{
},
};
/// Wraps an IRP_MJ_CREATE request (triggered when user-space opens the device handle).
pub struct CreateRequest<'a> {
irp: &'a mut IRP,
}
impl CreateRequest<'_> {
pub fn new(irp: &mut IRP) -> CreateRequest<'_> {
CreateRequest { irp }
}
/// Returns the PID of the process that opened the device handle.
/// Safe to call here because IRP_MJ_CREATE always runs in the context
/// of the initiating process, never in an arbitrary system thread.
pub fn get_requestor_pid(&self) -> u32 {
unsafe { crate::ffi::PsGetCurrentProcessId() as u32 }
}
pub fn complete(&mut self) {
// FILE_OPENED (1): indicates the device was opened (not created/superseded).
const FILE_OPENED: usize = 1;
self.irp.IoStatus.Information = FILE_OPENED;
self.irp.IoStatus.Anonymous.Status = STATUS_SUCCESS;
unsafe { IofCompleteRequest(self.irp, IO_NO_INCREMENT as i8) };
}
pub fn get_status(&self) -> NTSTATUS {
unsafe { self.irp.IoStatus.Anonymous.Status }
}
}
/// Wraps an IRP_MJ_CLEANUP request (triggered when user-space closes the last handle).
pub struct CleanupRequest<'a> {
irp: &'a mut IRP,
}
impl CleanupRequest<'_> {
pub fn new(irp: &mut IRP) -> CleanupRequest<'_> {
CleanupRequest { irp }
}
pub fn complete(&mut self) {
self.irp.IoStatus.Information = 0;
self.irp.IoStatus.Anonymous.Status = STATUS_SUCCESS;
unsafe { IofCompleteRequest(self.irp, IO_NO_INCREMENT as i8) };
}
pub fn get_status(&self) -> NTSTATUS {
unsafe { self.irp.IoStatus.Anonymous.Status }
}
}
pub struct ReadRequest<'a> {
irp: &'a mut IRP,
buffer: &'a mut [u8],