use alloc::sync::Arc;
use core::ptr::null_mut;
use vck_common::{VckError, VckResult};
use wdk_sys::{
ntddk::{
IoAttachDeviceToDeviceStackSafe, IoCreateDevice, IoDeleteDevice, IoDetachDevice,
IoGetDeviceObjectPointer, ObfDereferenceObject,
},
DEVICE_OBJECT, DO_BUFFERED_IO, DO_DEVICE_INITIALIZING, DO_DIRECT_IO, DO_POWER_PAGABLE,
DRIVER_OBJECT, FILE_DEVICE_DISK, FILE_READ_DATA, FILE_WRITE_DATA, PDEVICE_OBJECT, PFILE_OBJECT,
};
use crate::{
device::{DeviceExtension, DEVICE_EXTENSION_SIZE, DEVICE_KIND_FILTER},
nt::{nt_success, UnicodeString},
registry::AttachedVolume,
};
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn attach_filter(
driver: *mut DRIVER_OBJECT,
target_nt_path: &str,
volume: Arc<AttachedVolume>,
) -> VckResult<(PDEVICE_OBJECT, PDEVICE_OBJECT)> {
let mut filter_do: PDEVICE_OBJECT = null_mut();
let status = unsafe {
IoCreateDevice(
driver,
DEVICE_EXTENSION_SIZE,
null_mut(), FILE_DEVICE_DISK,
0,
0, &mut filter_do,
)
};
if !nt_success(status) {
return Err(VckError::Io("IoCreateDevice(filter) failed".into()));
}
let name = UnicodeString::new(target_nt_path);
let mut file_obj: PFILE_OBJECT = null_mut();
let mut target_do: PDEVICE_OBJECT = null_mut();
let status = unsafe {
IoGetDeviceObjectPointer(
name.as_ptr(),
FILE_READ_DATA | FILE_WRITE_DATA,
&mut file_obj,
&mut target_do,
)
};
if !nt_success(status) {
unsafe { IoDeleteDevice(filter_do) };
return Err(VckError::Io(
"IoGetDeviceObjectPointer(target) failed".into(),
));
}
let mut lower: PDEVICE_OBJECT = null_mut();
let status = unsafe { IoAttachDeviceToDeviceStackSafe(filter_do, target_do, &mut lower) };
unsafe { ObfDereferenceObject(file_obj.cast()) };
if !nt_success(status) || lower.is_null() {
unsafe { IoDeleteDevice(filter_do) };
return Err(VckError::Io(
"IoAttachDeviceToDeviceStackSafe failed".into(),
));
}
unsafe {
let ext = (*filter_do).DeviceExtension as *mut DeviceExtension;
(*ext).kind = DEVICE_KIND_FILTER;
(*ext).lower_device = lower;
(*ext).volume = Arc::into_raw(volume.clone());
(*ext).vthread = null_mut();
crate::filter::volume_thread::bind(filter_do, volume);
(*filter_do).Flags |= (*lower).Flags & (DO_BUFFERED_IO | DO_DIRECT_IO | DO_POWER_PAGABLE);
(*filter_do).DeviceType = (*lower).DeviceType;
(*filter_do).Characteristics = (*lower).Characteristics;
(*filter_do).Flags &= !DO_DEVICE_INITIALIZING;
}
crate::vck_log!(
"filter: attached filter={:p} lower={:p} (NOTE: may be above FSD if FSD already mounted)",
filter_do,
lower
);
Ok((filter_do, lower))
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn attach_filter_unbound(
driver: *mut DRIVER_OBJECT,
target_nt_path: &str,
) -> VckResult<(PDEVICE_OBJECT, PDEVICE_OBJECT)> {
let mut filter_do: PDEVICE_OBJECT = null_mut();
let status = unsafe {
IoCreateDevice(
driver,
DEVICE_EXTENSION_SIZE,
null_mut(),
FILE_DEVICE_DISK,
0,
0,
&mut filter_do,
)
};
if !nt_success(status) {
return Err(VckError::Io("IoCreateDevice(filter) failed".into()));
}
let name = UnicodeString::new(target_nt_path);
let mut file_obj: PFILE_OBJECT = null_mut();
let mut target_do: PDEVICE_OBJECT = null_mut();
let status = unsafe {
IoGetDeviceObjectPointer(
name.as_ptr(),
FILE_READ_DATA | FILE_WRITE_DATA,
&mut file_obj,
&mut target_do,
)
};
if !nt_success(status) {
unsafe { IoDeleteDevice(filter_do) };
return Err(VckError::Io(
"IoGetDeviceObjectPointer(unbound) failed".into(),
));
}
let mut lower: PDEVICE_OBJECT = null_mut();
let status = unsafe { IoAttachDeviceToDeviceStackSafe(filter_do, target_do, &mut lower) };
unsafe { ObfDereferenceObject(file_obj.cast()) };
if !nt_success(status) || lower.is_null() {
unsafe { IoDeleteDevice(filter_do) };
return Err(VckError::Io(
"IoAttachDeviceToDeviceStackSafe(unbound) failed".into(),
));
}
unsafe {
let ext = (*filter_do).DeviceExtension as *mut DeviceExtension;
(*ext).kind = DEVICE_KIND_FILTER;
(*ext).lower_device = lower;
(*ext).volume = null_mut();
(*ext).vthread = null_mut();
(*filter_do).Flags |= (*lower).Flags & (DO_BUFFERED_IO | DO_DIRECT_IO | DO_POWER_PAGABLE);
(*filter_do).DeviceType = (*lower).DeviceType;
(*filter_do).Characteristics = (*lower).Characteristics;
(*filter_do).Flags &= !DO_DEVICE_INITIALIZING;
}
Ok((filter_do, lower))
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn attach_filter_to_raw_device(
driver: *mut DRIVER_OBJECT,
target_do: PDEVICE_OBJECT,
) -> VckResult<(PDEVICE_OBJECT, PDEVICE_OBJECT)> {
let mut filter_do: PDEVICE_OBJECT = null_mut();
let status = unsafe {
IoCreateDevice(
driver,
DEVICE_EXTENSION_SIZE,
null_mut(),
FILE_DEVICE_DISK,
0,
0,
&mut filter_do,
)
};
if !nt_success(status) {
return Err(VckError::Io("IoCreateDevice(filter-raw) failed".into()));
}
let mut lower: PDEVICE_OBJECT = null_mut();
let status = unsafe { IoAttachDeviceToDeviceStackSafe(filter_do, target_do, &mut lower) };
if !nt_success(status) || lower.is_null() {
unsafe { IoDeleteDevice(filter_do) };
return Err(VckError::Io(
"IoAttachDeviceToDeviceStackSafe(raw) failed".into(),
));
}
unsafe {
let ext = (*filter_do).DeviceExtension as *mut DeviceExtension;
(*ext).kind = DEVICE_KIND_FILTER;
(*ext).lower_device = lower;
(*ext).volume = null_mut(); (*ext).vthread = null_mut();
(*filter_do).Flags |= (*lower).Flags & (DO_BUFFERED_IO | DO_DIRECT_IO | DO_POWER_PAGABLE);
(*filter_do).DeviceType = (*lower).DeviceType;
(*filter_do).Characteristics = (*lower).Characteristics;
(*filter_do).Flags &= !DO_DEVICE_INITIALIZING;
}
Ok((filter_do, lower))
}
pub unsafe fn filter_rebind_volume(filter_do: PDEVICE_OBJECT, volume: Arc<AttachedVolume>) {
let ext = (*filter_do).DeviceExtension as *mut DeviceExtension;
let old_ptr = (*ext).volume;
if !old_ptr.is_null() {
drop(Arc::from_raw(old_ptr));
}
(*ext).volume = Arc::into_raw(volume.clone());
crate::filter::volume_thread::bind(filter_do, volume);
}
pub unsafe fn filter_bind_volume(filter_do: PDEVICE_OBJECT, volume: Arc<AttachedVolume>) {
let ext = (*filter_do).DeviceExtension as *mut DeviceExtension;
(*ext).volume = Arc::into_raw(volume.clone());
crate::filter::volume_thread::bind(filter_do, volume);
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn attach_filter_to_device(
driver: *mut DRIVER_OBJECT,
target_do: PDEVICE_OBJECT,
volume: Arc<AttachedVolume>,
) -> VckResult<(PDEVICE_OBJECT, PDEVICE_OBJECT)> {
let mut filter_do: PDEVICE_OBJECT = null_mut();
let status = unsafe {
IoCreateDevice(
driver,
DEVICE_EXTENSION_SIZE,
null_mut(),
FILE_DEVICE_DISK,
0,
0,
&mut filter_do,
)
};
if !nt_success(status) {
return Err(VckError::Io("IoCreateDevice(filter) failed".into()));
}
let mut lower: PDEVICE_OBJECT = null_mut();
let status = unsafe { IoAttachDeviceToDeviceStackSafe(filter_do, target_do, &mut lower) };
if !nt_success(status) || lower.is_null() {
unsafe { IoDeleteDevice(filter_do) };
return Err(VckError::Io(
"IoAttachDeviceToDeviceStackSafe(device) failed".into(),
));
}
unsafe {
let ext = (*filter_do).DeviceExtension as *mut DeviceExtension;
(*ext).kind = DEVICE_KIND_FILTER;
(*ext).lower_device = lower;
(*ext).volume = Arc::into_raw(volume.clone());
(*ext).vthread = null_mut();
crate::filter::volume_thread::bind(filter_do, volume);
(*filter_do).Flags |= (*lower).Flags & (DO_BUFFERED_IO | DO_DIRECT_IO | DO_POWER_PAGABLE);
(*filter_do).DeviceType = (*lower).DeviceType;
(*filter_do).Characteristics = (*lower).Characteristics;
(*filter_do).Flags &= !DO_DEVICE_INITIALIZING;
}
Ok((filter_do, lower))
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn unbind_filter(filter_do: PDEVICE_OBJECT) {
if filter_do.is_null() {
return;
}
unsafe {
let ext = (*filter_do).DeviceExtension as *mut DeviceExtension;
if !(*ext).vthread.is_null() {
let vt = alloc::boxed::Box::from_raw((*ext).vthread);
vt.stop();
(*ext).vthread = null_mut();
}
if !(*ext).volume.is_null() {
drop(Arc::from_raw((*ext).volume));
(*ext).volume = null_mut();
}
}
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn detach_filter(filter_do: PDEVICE_OBJECT) {
if filter_do.is_null() {
return;
}
unsafe {
let ext = (*filter_do).DeviceExtension as *mut DeviceExtension;
if !(*ext).vthread.is_null() {
let vt = alloc::boxed::Box::from_raw((*ext).vthread);
vt.stop();
(*ext).vthread = null_mut();
}
if !(*ext).lower_device.is_null() {
IoDetachDevice((*ext).lower_device);
(*ext).lower_device = null_mut();
}
if !(*ext).volume.is_null() {
drop(Arc::from_raw((*ext).volume));
(*ext).volume = null_mut();
}
IoDeleteDevice(filter_do);
}
}
pub fn find_filter_for_volume<F, FN>(
nt_path: &str,
lookup_fn: F,
name_lookup_fn: FN,
) -> Option<(PDEVICE_OBJECT, PDEVICE_OBJECT)>
where
F: Fn(PDEVICE_OBJECT) -> Option<(PDEVICE_OBJECT, PDEVICE_OBJECT)>,
FN: Fn(&str) -> Option<(PDEVICE_OBJECT, PDEVICE_OBJECT)>,
{
use wdk_sys::ntddk::{IoGetDeviceAttachmentBaseRef, ObfDereferenceObject};
use wdk_sys::{FILE_READ_DATA, PFILE_OBJECT};
let name = UnicodeString::new(nt_path);
let mut file_obj: PFILE_OBJECT = null_mut();
let mut vol_do: PDEVICE_OBJECT = null_mut();
let status = unsafe {
IoGetDeviceObjectPointer(name.as_ptr(), FILE_READ_DATA, &mut file_obj, &mut vol_do)
};
if !nt_success(status) || vol_do.is_null() {
return None;
}
unsafe { ObfDereferenceObject(file_obj.cast()) };
let base_dev = unsafe { IoGetDeviceAttachmentBaseRef(vol_do) };
crate::vck_log!(
"find_filter: vol_do={:p} base_dev={:p} nt_path={}",
vol_do,
base_dev,
nt_path
);
let dev_name = unsafe {
use wdk_sys::ntddk::ObQueryNameString;
let mut buf = [0u8; 256];
let mut ret_len: u32 = 0;
let st = ObQueryNameString(vol_do.cast(), buf.as_mut_ptr().cast(), 256, &mut ret_len);
if st >= 0 {
let name_len = u16::from_le_bytes([buf[0], buf[1]]) as usize / 2;
let name_ptr = usize::from_le_bytes(buf[8..16].try_into().unwrap_or([0; 8]));
if name_len > 0 && name_ptr != 0 {
let chars = core::slice::from_raw_parts(name_ptr as *const u16, name_len.min(64));
let mut s = alloc::string::String::new();
for &c in chars {
if (0x20..0x7F).contains(&c) {
s.push(c as u8 as char);
} else {
s.push('?');
}
}
s
} else {
alloc::string::String::new()
}
} else {
alloc::string::String::new()
}
};
crate::vck_log!(
"find_filter: vol_do_name='{}' nt_path='{}'",
dev_name,
nt_path
);
if !dev_name.is_empty() {
if let Some(result) = name_lookup_fn(&dev_name) {
crate::vck_log!("find_filter: found via vol_do_name={}", dev_name);
return Some(result);
}
}
if let Some(result) = name_lookup_fn(nt_path) {
crate::vck_log!("find_filter: found via nt_path={}", nt_path);
return Some(result);
}
if !base_dev.is_null() {
unsafe { ObfDereferenceObject(base_dev.cast()) };
if let Some(result) = lookup_fn(base_dev) {
crate::vck_log!("find_filter: found via base_dev={:p}", base_dev);
return Some(result);
}
}
if let Some(result) = lookup_fn(vol_do) {
crate::vck_log!("find_filter: found via vol_do={:p}", vol_do);
return Some(result);
}
let top_dev = unsafe { wdk_sys::ntddk::IoGetAttachedDeviceReference(vol_do) };
if !top_dev.is_null() {
let result = lookup_fn(top_dev);
unsafe { wdk_sys::ntddk::ObfDereferenceObject(top_dev.cast()) };
if let Some(r) = result {
crate::vck_log!("find_filter: found via top_dev={:p}", top_dev);
return Some(r);
}
}
if let Some(result) = find_our_filter_in_stack(nt_path) {
crate::vck_log!("find_filter: found via stack walk for {}", nt_path);
return Some(result);
}
crate::vck_log!("find_filter: not found for {}", nt_path);
None
}
pub fn find_our_filter_in_stack(nt_path: &str) -> Option<(PDEVICE_OBJECT, PDEVICE_OBJECT)> {
use wdk_sys::ntddk::{IoGetLowerDeviceObject, ObfDereferenceObject};
use wdk_sys::{FILE_READ_DATA, PFILE_OBJECT};
use wdk_sys::ntddk::IoGetAttachedDeviceReference;
let name = UnicodeString::new(nt_path);
let mut file_obj: PFILE_OBJECT = null_mut();
let mut vol_do: PDEVICE_OBJECT = null_mut();
let status = unsafe {
IoGetDeviceObjectPointer(name.as_ptr(), FILE_READ_DATA, &mut file_obj, &mut vol_do)
};
if !nt_success(status) || vol_do.is_null() {
return None;
}
unsafe { ObfDereferenceObject(file_obj.cast()) };
let top_do = unsafe { IoGetAttachedDeviceReference(vol_do) };
if top_do.is_null() {
return None;
}
unsafe {
let mut current = top_do;
let mut depth = 0u32;
loop {
let ext = (*current).DeviceExtension as *const DeviceExtension;
let kind = if !ext.is_null() {
(*ext).kind
} else {
0xFFFF_FFFF
};
crate::vck_log!(
"find_filter[{}]: do={:p} ext_kind=0x{:08x}",
depth,
current,
kind
);
if !ext.is_null() && (*ext).kind == DEVICE_KIND_FILTER {
let lower = (*ext).lower_device;
ObfDereferenceObject(current.cast());
crate::vck_log!(
"find_filter: found filter_do={:p} lower={:p}",
current,
lower
);
return Some((current, lower));
}
let next = IoGetLowerDeviceObject(current);
ObfDereferenceObject(current.cast());
if next.is_null() {
crate::vck_log!("find_filter: reached base (depth={})", depth);
break;
}
current = next;
depth += 1;
if depth > 12 {
break;
}
}
}
None
}
pub type FilterDevice = *mut DEVICE_OBJECT;