fsfilter_rs/
driver_comm.rs

1//! Low-level communication with the minifilter.
2
3use std::mem;
4use std::os::raw::{c_ulong, c_ulonglong};
5use std::ptr;
6
7use sysinfo::{get_current_pid, Pid, PidExt};
8use wchar::wchar_t;
9use widestring::U16CString;
10use windows::core::{HRESULT, PCSTR, PCWSTR};
11use windows::Win32::Foundation::{CloseHandle, HANDLE};
12use windows::Win32::Storage::FileSystem::GetDriveTypeA;
13use windows::Win32::Storage::InstallableFileSystems::{
14    FilterConnectCommunicationPort, FilterSendMessage,
15};
16
17use crate::driver_comm::DriveType::{
18    DriveCDRom, DriveFixed, DriveNoRootDir, DriveRamDisk, DriveRemote, DriveRemovable, DriveUnknown,
19};
20use crate::driver_comm::IrpMajorOp::{IrpCreate, IrpNone, IrpRead, IrpSetInfo, IrpWrite};
21use crate::shared_def;
22use crate::shared_def::ReplyIrp;
23
24type BufPath = [wchar_t; 520];
25
26/// The user-mode app (this app) can send several messages types to the driver. See [`DriverComMessageType`]
27/// for details.
28/// Depending on the message type, the *pid*, *gid* and *path* fields can be optional.
29#[derive(Debug)]
30#[repr(C)]
31struct DriverComMessage {
32    /// The type message to send. See [`DriverComMessageType`].
33    r#type: c_ulong,
34    /// The pid of the process which triggered an i/o activity;
35    pid: c_ulong,
36    /// The gid is maintained by the driver
37    gid: c_ulonglong,
38    path: BufPath,
39}
40
41/// Messages types to send directives to the minifilter, by using te [`DriverComMessage`] struct.
42#[repr(C)]
43#[allow(dead_code)]
44enum DriverComMessageType {
45    /// Not used yet. The minifilter has the ability to monitor a specific part of the fs.
46    AddScanDirectory,
47    /// Not used yet. The minifilter has the ability to monitor a specific part of the fs.
48    RemScanDirectory,
49    /// Ask for a [`ReplyIrp`], if any available.
50    GetOps,
51    /// Set this app pid to the minifilter (related IRPs will be ignored);
52    SetPid,
53    /// Instruct the minifilter to kill all pids in the family designated by a given gid.
54    KillGid,
55}
56
57/// A minifilter is identified by a port (know in advance), like a named pipe used for communication,
58/// and a handle, retrieved by [`open_kernel_driver_com`](Self::open_kernel_driver_com).
59#[derive(Debug)]
60#[repr(C)]
61pub struct Driver {
62    handle: HANDLE,
63}
64
65impl Driver {
66    /// Can be used to properly close the communication (and unregister) with the minifilter.
67    /// If this fn is not used and the program has stopped, the handle is automatically closed,
68    /// seemingly without any side-effects.
69    #[inline]
70    pub fn close_kernel_communication(&self) -> bool {
71        unsafe { CloseHandle(self.handle).as_bool() }
72    }
73
74    /// The user-mode running app (this one) has to register itself to the driver.
75    ///
76    /// # Panics
77    /// This fn panics if it is unable to get the current pid.
78    ///
79    /// # Errors
80    /// This fn returns an error if it is unable to register itself to the minifilter.
81    #[inline]
82    pub fn driver_set_app_pid(&self) -> Result<(), windows::core::Error> {
83        let buf = Self::string_to_commessage_buffer(r"\Device\harddiskVolume");
84
85        let mut get_irp_msg: DriverComMessage = DriverComMessage {
86            r#type: DriverComMessageType::SetPid as c_ulong,
87            pid: get_current_pid().unwrap().as_u32() as c_ulong,
88            gid: 140_713_315_094_899,
89            path: buf, //wch!("\0"),
90        };
91        let mut tmp: u32 = 0;
92
93        unsafe {
94            FilterSendMessage(
95                self.handle,
96                ptr::addr_of_mut!(get_irp_msg).cast::<std::ffi::c_void>(),
97                mem::size_of::<DriverComMessage>() as c_ulong,
98                None,
99                0,
100                std::ptr::addr_of_mut!(tmp),
101            )
102        }
103    }
104
105    /// Try to open a com canal with the minifilter before this app is registered.
106    ///
107    /// # Panics
108    /// This function will panic if the minifilter port has any nul value (except the last one)
109    /// in it's name.
110    ///
111    /// # Errors
112    /// This fn can fail is the minifilter is unreachable:
113    ///
114    /// * if it is not started (try `sc start snFilter` first
115    /// * if a connection is already established: it can accepts only one at a time.
116    ///
117    /// In that case the Error is raised by the OS (`windows::Error`) and is generally readable.
118    #[inline]
119    pub fn open_kernel_driver_com() -> Result<Self, windows::core::Error> {
120        let com_port_name = U16CString::from_str("\\snFilter").unwrap().into_raw();
121        let handle;
122        unsafe {
123            handle = FilterConnectCommunicationPort(PCWSTR(com_port_name), 0, None, 0, None)?;
124        }
125        let res = Self { handle };
126        Ok(res)
127    }
128
129    /// Ask the driver for a [`ReplyIrp`], if any. This is a low-level function and the returned object
130    /// uses C pointers. Managing C pointers requires a special care, because of the Rust timelines.
131    /// [`ReplyIrp`] is optional since the minifilter returns null if there is no new activity.
132    ///
133    /// # Panics
134    /// This fn panics if it is unable to get the current pid or cannot get driver message from the
135    /// minifilter.
136    #[inline]
137    pub fn get_irp(&self, vecnew: &mut Vec<u8>) -> Option<ReplyIrp> {
138        let mut get_irp_msg = Self::build_irp_msg(
139            DriverComMessageType::GetOps,
140            get_current_pid().unwrap(),
141            0,
142            "",
143        );
144        let mut tmp: u32 = 0;
145
146        unsafe {
147            FilterSendMessage(
148                self.handle,
149                ptr::addr_of_mut!(get_irp_msg).cast::<std::ffi::c_void>(),
150                mem::size_of::<DriverComMessage>() as c_ulong,
151                Option::from(vecnew.as_mut_ptr().cast::<std::ffi::c_void>()),
152                65536_u32,
153                ptr::addr_of_mut!(tmp).cast::<u32>(),
154            )
155            .expect("Cannot get driver message from driver");
156        }
157
158        if tmp != 0 {
159            let reply_irp: ReplyIrp;
160            unsafe {
161                reply_irp =
162                    std::ptr::read_unaligned(vecnew.as_ptr().cast::<shared_def::ReplyIrp>());
163            }
164            return Some(reply_irp);
165        }
166        None
167    }
168
169    /// Ask the minifilter to kill all pids related to the given *gid*. Pids are killed in driver-mode
170    /// by calls to `NtClose`.
171    #[inline]
172    pub fn try_kill(&self, gid: c_ulonglong) -> Result<HRESULT, windows::core::Error> {
173        let mut killmsg = DriverComMessage {
174            r#type: DriverComMessageType::KillGid as c_ulong,
175            pid: 0, //get_current_pid().unwrap() as u32,
176            gid,
177            path: [0; 520],
178        };
179        let mut res: i32 = 0;
180        let mut res_size: u32 = 0;
181
182        unsafe {
183            FilterSendMessage(
184                self.handle,
185                ptr::addr_of_mut!(killmsg).cast::<std::ffi::c_void>(),
186                mem::size_of::<DriverComMessage>() as c_ulong,
187                Option::from(ptr::addr_of_mut!(res).cast::<std::ffi::c_void>()),
188                4_u32,
189                ptr::addr_of_mut!(res_size).cast::<u32>(),
190            )?;
191        }
192
193        Ok(HRESULT(res))
194    }
195
196    #[inline]
197    fn string_to_commessage_buffer(bufstr: &str) -> BufPath {
198        let temp = U16CString::from_str(bufstr).unwrap();
199        let mut buf: BufPath = [0; 520];
200        for (i, c) in temp.as_slice_with_nul().iter().enumerate() {
201            buf[i] = *c as wchar_t;
202        }
203        buf
204    }
205
206    // TODO: move to ComMessage?
207    #[inline]
208    fn build_irp_msg(
209        commsgtype: DriverComMessageType,
210        pid: Pid,
211        gid: u64,
212        path: &str,
213    ) -> DriverComMessage {
214        DriverComMessage {
215            r#type: commsgtype as c_ulong, // SetPid
216            pid: pid.as_u32() as c_ulong,
217            gid,
218            path: Self::string_to_commessage_buffer(path),
219        }
220    }
221}
222
223/// See [`IOMessage`](crate::shared_def::IOMessage) struct and
224/// [this doc](https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/irp-major-function-codes).
225#[repr(C)]
226pub enum IrpMajorOp {
227    /// Nothing happened
228    IrpNone,
229    /// On read, any time following the successful completion of a create request.
230    IrpRead,
231    /// On write, any time following the successful completion of a create request.
232    IrpWrite,
233    /// Set Metadata about a file or file handle. In that case, [`FileChangeInfo`](crate::shared_def::FileChangeInfo) indicates
234    /// the nature of the modification.
235    IrpSetInfo,
236    /// Open a handle to a file object or device object.
237    IrpCreate,
238    /// File object handle has been closed
239    IrpCleanUp,
240}
241
242impl IrpMajorOp {
243    #[inline]
244    #[must_use]
245    pub fn from_byte(b: u8) -> Self {
246        match b {
247            // 0 => IrpNone,
248            1 => IrpRead,
249            2 => IrpWrite,
250            3 => IrpSetInfo,
251            4 | 5 => IrpCreate,
252            _ => IrpNone,
253        }
254    }
255}
256
257/// See [`IOMessage`](crate::shared_def::IOMessage) struct and
258/// [this doc](https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getdrivetypea).
259#[repr(C)]
260pub enum DriveType {
261    /// The drive type cannot be determined.
262    DriveUnknown,
263    /// The root path is invalid; for example, there is no volume mounted at the specified path.
264    DriveNoRootDir,
265    /// The drive has removable media; for example, a floppy drive, thumb drive, or flash card reader.
266    DriveRemovable,
267    /// The drive has fixed media; for example, a hard disk drive or flash drive.
268    DriveFixed,
269    /// The drive is a remote (network) drive.
270    DriveRemote,
271    /// The drive is a CD-ROM drive.
272    DriveCDRom,
273    /// The drive is a RAM disk.
274    DriveRamDisk,
275}
276
277impl DriveType {
278    /// Determines whether a disk drive is a removable, fixed, CD-ROM, RAM disk, or network drive.
279    ///
280    /// # Panics
281    /// Will panic if drive path is invalid.
282    #[inline]
283    #[must_use]
284    pub fn from_filepath(filepath: &str) -> Self {
285        let mut drive_type = 1u32;
286        if !filepath.is_empty() {
287            let drive_path = &filepath[..=filepath.find('\\').unwrap()];
288            unsafe {
289                drive_type = GetDriveTypeA(PCSTR(String::from(drive_path).as_ptr()));
290            }
291        }
292        match drive_type {
293            0 => DriveUnknown,
294            // 1 => DriveNoRootDir,
295            2 => DriveRemovable,
296            3 => DriveFixed,
297            4 => DriveRemote,
298            5 => DriveCDRom,
299            6 => DriveRamDisk,
300            _ => DriveNoRootDir,
301        }
302    }
303}