Filtering disk IO

Hello list.

I have a mini filter driver that monitors several events in kernel and also wants to know about I/O performed on disk drives. The driver gets notifications if a disk is opened via \.\PhysicalDrive%d and doesnā€™t get them if a disk is opened via device interface.

It looks that the problem is related to ā€˜direct openā€™ but I am not really sure. So, how it could be solved assuming that I want to get notifications about all IOs performed on a disk regardless of how it was opened? Is disk upper filter the only solution in this case?

Thanks.

Hm, should I crosspost to NTFSD to get more attention?

The symbolic link and device interface GUID paths should be equivalent. Did
you dump the resulting handle from each open (!handle) and see where each
points to?

There can be lots of things in play here. The first thing to note is that
there are (effectively) four types of mountable device objects in Windows:

  1. Disk
  2. Volume
  3. CD-ROM
  4. Tape

If you attempt to open any of these device object types by name and *request
data access*, the I/O Manager triggers mount processing. If no file system
claims the underlying media as being their own then the I/O Managerā€™s RAW
file system claims the device. I/O requests against the resulting file
object are then targeted by the RAW file system.

This can make for some ugly combinations. For example, you may have a volume
that contains a partition from an existing disk. The volume may have NTFS
mounted on it, but if you open the underlying disk directly you get a RAW
file system instance (the volume is formatted with NTFS, not the disk). A
file system filter would have separate instances for each of these (one for
NTFS and one for RAW).

In the cases where the caller doesnā€™t ask for data access, the open goes to
the top of the device stack for the specified device (disk/volume/cd/tape).
No file systems are involved, which shouldnā€™t matter anyway as there is no
ability to perform data access.

Hence my suggestion to dump the resulting handle and see where the
underlying file object is pointing.

Of course, all of the above only applies to opens from user mode. A kernel
mode component can do whatever they want, so they could send I/Os directly
to the disk and bypass the file system (or the disk driver, for that
matter). For example, the kernel mode portion of the shadow copy service
writes directly to the COW area on the disk without using the file system.
The only way to see these writes would be from a disk filter.

-scott
OSR
@OSRDrivers

wrote in message news:xxxxx@ntdevā€¦

Hello list.

I have a mini filter driver that monitors several events in kernel and also
wants to know about I/O performed on disk drives. The driver gets
notifications if a disk is opened via \.\PhysicalDrive%d and doesnā€™t get
them if a disk is opened via device interface.

It looks that the problem is related to ā€˜direct openā€™ but I am not really
sure. So, how it could be solved assuming that I want to get notifications
about all IOs performed on a disk regardless of how it was opened? Is disk
upper filter the only solution in this case?

Thanks.

> matter). For example, the kernel mode portion of the shadow copy service

writes directly to the COW area on the disk without using the file system.
The only way to see these writes would be from a disk filter.

Are they using the DASD path in the FSD? or bypassing FSD totally?

I really, really expect that the first is the answer :slight_smile:

ā€“
Maxim S. Shatskih
Microsoft MVP on File System And Storage
xxxxx@storagecraft.com
http://www.storagecraft.com

Handle for:

h = CreateFileA(ā€œ\\.\PhysicalDrive0ā€, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, 0, 0);

looks like this:

kd> !handle a4 f

PROCESS 83cf3440 SessionId: 1 Cid: 0fb4 Peb: 7ffd9000 ParentCid: 0298
DirBase: 1ea284e0 ObjectTable: 83e18bc8 HandleCount: 41.
Image: ScratchPad.exe

Handle table at 83e18bc8 with 41 entries in use

00a4: Object: 85043400 GrantedAccess: 0012019f Entry: 83e49148
Object: 85043400 Type: (83a2ab90) File
ObjectHeader: 850433e8 (new version)
HandleCount: 1 PointerCount: 1

kd> !fileobj 85043400

Device Object: 0x849e97b8 \Driver\Disk
Vpb: 0x849e9730
Event signalled
Access: Read Write SharedRead SharedWrite SharedDelete

Flags: 0x44000a
Synchronous IO
No Intermediate Buffering
Handle Created
Volume Open

FsContext: 0x83acf930 FsContext2: 0x00000000
CurrentByteOffset: 0

kd> !devstack 0x849e97b8
!DevObj !DrvObj !DevExt ObjectName
849e93f8 \Driver\partmgr 849e94b0

849e97b8 \Driver\Disk 849e9870 DR0
849e9ef0 \Driver\storflt 849e8aa0
848cf020 \Driver\ACPI 83a4a770
848cc908 \Driver\atapi 848cc9c0 IdeDeviceP0T0L0-0
!DevNode 848d7950 :
DeviceInst is ā€œIDE\DiskVirtual_HD______________________________1.1.0___\5&35dc7040&0&0.0.0ā€
ServiceName is ā€œdiskā€

When nt!ntWriteFile calls IoGetRelatedDeviceObject the function switches to another stack resulting in:

kd> !devstack @eax
!DevObj !DrvObj !DevExt ObjectName

8508a980 \FileSystem\FltMgr 8508aa38
83acf878 \FileSystem\RAW 83acf930

It looks different when a disk is opened by interface symlink:

h = CreateFileW(pDetailData->DevicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, 0);

//I tried with and without FILE_FLAG_NO_BUFFERING

kd> !handle dc

PROCESS 83cf3440 SessionId: 1 Cid: 0fb4 Peb: 7ffd9000 ParentCid: 0298
DirBase: 1ea284e0 ObjectTable: 83e18bc8 HandleCount: 55.
Image: ScratchPad.exe

Handle table at 83e18bc8 with 55 entries in use

00dc: Object: 85040b48 GrantedAccess: 0012019f Entry: 83e491b8
Object: 85040b48 Type: (83a2ab90) File
ObjectHeader: 85040b30 (new version)
HandleCount: 1 PointerCount: 1

kd> !fileobj 85040b48

Device Object: 0x848cc908 \Driver\atapi
Vpb is NULL
Event signalled

Flags: 0x4000a
Synchronous IO
No Intermediate Buffering
Handle Created

CurrentByteOffset: 0

kd> !devstack 0x848cc908
!DevObj !DrvObj !DevExt ObjectName
849e93f8 \Driver\partmgr 849e94b0
849e97b8 \Driver\Disk 849e9870 DR0
849e9ef0 \Driver\storflt 849e8aa0
848cf020 \Driver\ACPI 83a4a770

848cc908 \Driver\atapi 848cc9c0 IdeDeviceP0T0L0-0
!DevNode 848d7950 :
DeviceInst is ā€œIDE\DiskVirtual_HD______________________________1.1.0___\5&35dc7040&0&0.0.0ā€
ServiceName is ā€œdiskā€

So VPB is NULL and IoGetRelatedDeviceObject doesnt switch stacks:

kd> !devstack @eax
!DevObj !DrvObj !DevExt ObjectName

849e93f8 \Driver\partmgr 849e94b0
849e97b8 \Driver\Disk 849e9870 DR0
849e9ef0 \Driver\storflt 849e8aa0
848cf020 \Driver\ACPI 83a4a770
848cc908 \Driver\atapi 848cc9c0 IdeDeviceP0T0L0-0
!DevNode 848d7950 :
DeviceInst is ā€œIDE\DiskVirtual_HD______________________________1.1.0___\5&35dc7040&0&0.0.0ā€
ServiceName is ā€œdiskā€

Does it mean that a user mode application might open a disk bypassing FltMgr? Why opening by interface differs from open by legacy symlink?

The FSD is somewhat involved in that files are used for the storage.
However, actual I/O to the files goes directly down the volume stack and
into the disk stack.

-scott
OSR
@OSRDrivers

ā€œMaxim S. Shatskihā€ wrote in message news:xxxxx@ntdevā€¦

matter). For example, the kernel mode portion of the shadow copy service
writes directly to the COW area on the disk without using the file system.
The only way to see these writes would be from a disk filter.

Are they using the DASD path in the FSD? or bypassing FSD totally?

I really, really expect that the first is the answer :slight_smile:

ā€“
Maxim S. Shatskih
Microsoft MVP on File System And Storage
xxxxx@storagecraft.com
http://www.storagecraft.com

Thanks for the debugger output, that certainly clears things up.

This is really cute and I must now admit that I clearly never fully thought
through this particular case. You are absolutely correct, the device
interface takes a different path than the symbolic link name. The reason why
is in the output you providedā€¦

In the symbolic link case, the file object is an open instance of a Disk
device object:

kd> !fileobj 85043400
Device Object: 0x849e97b8 \Driver\Disk
Vpb: 0x849e9730

This is due to the fact that Disk calls IoCreateSymbolicLink and points this
link to its own FDO. Note the presence of the VPB, hence the I/O requests
are going to traverse to the file system stack.

However, in the case of the device interface the symbolic link that you
ultimately open points to the diskā€™s PDO (created by ATAPI):

kd> !fileobj 85040b48
Device Object: 0x848cc908 \Driver\atapi

This must not have been created with a device type of FILE_DEVICE_DISK
because it doesnā€™t have a VPB. If thereā€™s no VPB, the I/O request goes to
the top of the device stack (which would be to PartMgr in this case).

(As an aside: you donā€™t get this problem in the volume stack because the
symbolic link name (HarddiskVolumeX) and the device interface both point to
the PDO created by volmgr, which has a VPB.)

So, you get two I/O paths to the disk from user mode. This makes for some
fun trivia points such as one path (PhysicalDriveX) implements share access
and the other (device interface) does not. Therefore you can open
PhysicalDriveX exclusively, but that doesnā€™t do you any good if someone else
opens via device interface.

Neat stuff and it highlights the problems with having multiple names in the
device stack. Bottom line: get yourself a disk filter if you really care
about this.

-scott
OSR
@OSRDrivers

wrote in message news:xxxxx@ntdevā€¦

Handle for:

h = CreateFileA(ā€œ\\.\PhysicalDrive0ā€, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
0, 0);

looks like this:

kd> !handle a4 f

PROCESS 83cf3440 SessionId: 1 Cid: 0fb4 Peb: 7ffd9000 ParentCid: 0298
DirBase: 1ea284e0 ObjectTable: 83e18bc8 HandleCount: 41.
Image: ScratchPad.exe

Handle table at 83e18bc8 with 41 entries in use

00a4: Object: 85043400 GrantedAccess: 0012019f Entry: 83e49148
Object: 85043400 Type: (83a2ab90) File
ObjectHeader: 850433e8 (new version)
HandleCount: 1 PointerCount: 1

kd> !fileobj 85043400

Device Object: 0x849e97b8 \Driver\Disk
Vpb: 0x849e9730
Event signalled
Access: Read Write SharedRead SharedWrite SharedDelete

Flags: 0x44000a
Synchronous IO
No Intermediate Buffering
Handle Created
Volume Open

FsContext: 0x83acf930 FsContext2: 0x00000000
CurrentByteOffset: 0

kd> !devstack 0x849e97b8
!DevObj !DrvObj !DevExt ObjectName
849e93f8 \Driver\partmgr 849e94b0

849e97b8 \Driver\Disk 849e9870 DR0
849e9ef0 \Driver\storflt 849e8aa0
848cf020 \Driver\ACPI 83a4a770
848cc908 \Driver\atapi 848cc9c0 IdeDeviceP0T0L0-0
!DevNode 848d7950 :
DeviceInst is
ā€œIDE\DiskVirtual_HD______________________________1.1.0___\5&35dc7040&0&0.0.0ā€
ServiceName is ā€œdiskā€

When nt!ntWriteFile calls IoGetRelatedDeviceObject the function switches to
another stack resulting in:

kd> !devstack @eax
!DevObj !DrvObj !DevExt ObjectName

8508a980 \FileSystem\FltMgr 8508aa38
83acf878 \FileSystem\RAW 83acf930

It looks different when a disk is opened by interface symlink:

h = CreateFileW(pDetailData->DevicePath, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
FILE_FLAG_NO_BUFFERING, 0);

//I tried with and without FILE_FLAG_NO_BUFFERING

kd> !handle dc

PROCESS 83cf3440 SessionId: 1 Cid: 0fb4 Peb: 7ffd9000 ParentCid: 0298
DirBase: 1ea284e0 ObjectTable: 83e18bc8 HandleCount: 55.
Image: ScratchPad.exe

Handle table at 83e18bc8 with 55 entries in use

00dc: Object: 85040b48 GrantedAccess: 0012019f Entry: 83e491b8
Object: 85040b48 Type: (83a2ab90) File
ObjectHeader: 85040b30 (new version)
HandleCount: 1 PointerCount: 1

kd> !fileobj 85040b48

Device Object: 0x848cc908 \Driver\atapi
Vpb is NULL
Event signalled

Flags: 0x4000a
Synchronous IO
No Intermediate Buffering
Handle Created

CurrentByteOffset: 0

kd> !devstack 0x848cc908
!DevObj !DrvObj !DevExt ObjectName
849e93f8 \Driver\partmgr 849e94b0
849e97b8 \Driver\Disk 849e9870 DR0
849e9ef0 \Driver\storflt 849e8aa0
848cf020 \Driver\ACPI 83a4a770

848cc908 \Driver\atapi 848cc9c0 IdeDeviceP0T0L0-0
!DevNode 848d7950 :
DeviceInst is
ā€œIDE\DiskVirtual_HD______________________________1.1.0___\5&35dc7040&0&0.0.0ā€
ServiceName is ā€œdiskā€

So VPB is NULL and IoGetRelatedDeviceObject doesnt switch stacks:

kd> !devstack @eax
!DevObj !DrvObj !DevExt ObjectName

849e93f8 \Driver\partmgr 849e94b0
849e97b8 \Driver\Disk 849e9870 DR0
849e9ef0 \Driver\storflt 849e8aa0
848cf020 \Driver\ACPI 83a4a770
848cc908 \Driver\atapi 848cc9c0 IdeDeviceP0T0L0-0
!DevNode 848d7950 :
DeviceInst is
ā€œIDE\DiskVirtual_HD______________________________1.1.0___\5&35dc7040&0&0.0.0ā€
ServiceName is ā€œdiskā€

Does it mean that a user mode application might open a disk bypassing
FltMgr? Why opening by interface differs from open by legacy symlink?

> This is due to the fact that Disk calls IoCreateSymbolicLink and points this

link to its own FDO.

Great to know that the disks are also such, not only CD/DVD drives :slight_smile:

The same issue is with CD/DVD drives: the \.\D: name, and the PnP devinterface name, reference the different DOs - one references the FDO and another the PDO in the same stack.

Then the ACL of the referenced DO is used to check the access rights.

This is since Vista I think. Iā€™ve noticed it around 2008.

And yes, this introduces some complexity to ā€œopen SCSI deviceā€ path to send SPTI/MMC commands to the CD/DVD drive.

ā€“
Maxim S. Shatskih
Microsoft MVP on File System And Storage
xxxxx@storagecraft.com
http://www.storagecraft.com

volsnap (shadow copy service) writes directly lower volume drivers (volmgr). sending writes above itself is a locking nightmare. volsnap also writes to the BPB. it gets a bit tricky however as it needs detect format and other FS operations.