Building virus-scanning functionality with file system filter drivers
As our reliance on computers has grown, so have the networks that
connect computers. We typically connect computers via a LAN, and either directly
or indirectly via the Internet. Although this connectivity facilitates sharing
programs and documents, it heightens the risk of infecting files with annoying
or destructive viruses. Consequently, you rarely find a Windows NT system that
doesn't run a virus-scanning product to check files for the presence of viruses
and prevent them from entering the system.
This month, I'll explore the internals of on-access virus scanners for NT.
First, I'll briefly describe how on-access virus scanners work. Next, because
on-access virus scanners work with file system drivers to check files for
viruses, I'll introduce how file system drivers (FAT, NTFS, etc.) interact with
NT through the I/O Manager. I'll conclude by describing where on-access virus
scanners fit into NT.
Virus Scanning Basics
Virus scanners check files in one of two ways. An on-demand virus
scanner examines every file on a disk (or within subdirectories that you
specify) and searches for viruses it knows about or for signatures common to
certain types of viruses. You can usually trigger on-demand virus scanners
manually, or automatically at regular intervals (e.g., during system boot). The
drawback of on-demand virus detection is that files you download or copy onto
the system can infect the computer before the virus scanner checks for viruses.
On-access virus scanning is proactive. On-access virus scanners
stop virus activation because the scanners check files at the time you open or
execute them. Thus, if you download an infected Microsoft Word document to your
hard disk, before Word can open the file, the virus scanner makes sure the file
is clean. When the virus scanner detects a virus in a file, the scanner either
removes the virus or returns an error code to the application opening the file
to prevent the open operation from proceeding.
Virus-scanning products for NT perform either or both types of virus
scanning. Implementing on-demand virus scanning is relatively straightforward:
The scanner opens the files and looks for signs of viruses. A Win32 program that
uses standard APIs can easily provide this functionality.
Implementing an on-access virus scanner is much trickier. You cannot use
the Win32 API to direct NT to check files whenever other programs (including NT)
open or execute the files. Furthermore, scanning must be transparent to the
applications running on the system. For example, Word must be able to open files
in its usual manner. The only way to provide this functionality is to write a
special type of device driver known as a file system filter driver.
The I/O Manager and File Systems
File system filter drivers hook themselves on top of file systems so that
they can intercept requests headed toward the file systems. The drivers review
each request and reject it, pass it to the file system, or change it on the way
to the file system. The drivers can also examine the results of requests on the
way back from the file systems. On-access virus scanners ensure that files are
free of viruses when you open them, so the file system filter drivers process
only open requests.
To better understand this design, you need to understand how file systems
are integrated with NT and how a file system services I/O requests (e.g., from a
Win32 program). The following description generally holds true for all types of
file system requests (e.g., create, read, write), but I'll concentrate on what
happens when a typical Win32 program uses the ReadFile API to read from a file.
Figure 1 shows the main system components involved in servicing the read
request. NT implements the ReadFile API in Kernel32.dll, a standard NT component
that's part of the Win32 subsystem. Kernel32 is the library in which NT
implements all file, process, and memory-management Win32 APIs. Some APIs, such
as CreateProcess, require that the library send messages to the Win32 subsystem
process to service the APIs. However, most APIs (including ReadFile) do not.
Kernel32 takes the parameters passed in the ReadFile call and constructs a call
to NT's kernel-mode API. In the kernel-mode API (also known as NT's native API),
all the functions begin with "Nt." The kernel-mode function for
reading a file is NtReadFile.
When Kernel32 issues a native NT API call, the processor switches into
kernel mode, and the native API call enters the NT kernel. All native API calls
enter through the same doorway: The kernel funnels requests to the kernel-mode
function that handles them. The kernel-mode NtReadFile function constructs an
I/O request packet (IRP) and initializes it with all the information to describe
the request (e.g., which file to read from, the starting offset and length of
the read, and the buffer that will receive the data on successful completion).
NtReadFile calls the I/O Manager to send the IRP to the file system of the drive
where the file resides. For example, if the C drive is an NTFS drive, reading
C:\mark1 causes the I/O Manager to call the NTFS driver.
File systems resolve some requests without ever touching the disk. In the
read example, if the data resides in the file system cache, NTFS completes the
IRP immediately. If the cache does not contain the data, NTFS must create one or
more new IRPs that instruct the hardware device driver managing the C drive's
hard disk to fetch the data from the disk. When the driver completes these IRPs,
NTFS can complete the IRP that NtReadFile sent. When the target file system
completes NtReadFile's IRP, the call to ReadFile ends and control returns to the
Win32 program; the program can now look at the file data.