![](/uploads/1/2/7/1/127164007/110105058.jpg)
Filtering File Systems - Then Things You Should KnowThe NT Insider, Volume 16, Issue 1, Jan-Feb 2009 Published: 09-Mar-09 Modified: 09-Mar-09Regardless of whether you are still supporting legacy file system filter drivers, or maintaining or building new mini-filter drivers, there are some common techniques (ok, there are only nine?) you can use to improve the robustness of your filter. Many of these are pragmatic - they are not things you will read in the documentation, they come from experience working with filters for many, many years.Never Trust BuffersFilter drivers are forced to endure a remarkably hostile environment, both from their neighbors (other filter drivers) as well as those that are inherently untrusted (any user mode code). A single mistake in a file system filter driver can create a potential 'time bomb' waiting to happen.User Buffers are the most commonly identified 'risk factor' when it comes to buffer management. Despite that, we still routinely see filters that fail to take the two necessary steps for protecting against user buffers.
Starting in Windows 10 version 1607, administrators and driver developers can use a registry setting to block legacy file system filter drivers. Legacy file system filter drivers are drivers that attach to the file system stack directly and don't use Filter Manager. For optimal reliability and performance, it's recommended to use file system minifilter drivers instead of legacy file system.
First, you must ensure that a user buffer really is within the user portion of the address space. This is performed by probing the address, normally using ProbeForRead or ProbeForWrite.Probing a user buffer only ensures it is in the user's address space, however. If it is invalid at that point in time the probe will work, but this call does nothing to ensure the address remains valid. Thus, the second key part of buffer handling is to ensure any access to it is protected with a structured exception handler. Microsoft provides their own custom handler and it is invoked using special extensions implemented by the C compiler used in the WDK environment.
This is done using try and except.Both of these steps are essential to ensuring correct user buffer handling.In addition, file system filter drivers must deal with the pragmatic issue that they may be handed invalid kernel data structures. The harsh reality is that it is not uncommon for other kernel components to pass along invalid structures. Mini-filters are somewhat protected against this, since invalid structures passed into filter manager will cause filter manager to crash, not the mini-filter. Despite this, if you are the only mini-filter on the system it won't crash without your filter and it will crash with your filter (and we all know that this means it must be 'your fault').Thus, what we generally suggest - as much as possible - is to carefully validate your own data structures. While not guaranteed, we've used this technique to detect erroneous situations numerous times.
In our experience it is better to use 'deprecated' functions like MmIsNonPagedSystem AddressValid (which is of dubious value) to add even a modest amount of robustness to our code. This is pragmatic - if the system crashes in our driver, we will be blamed for the crash. If we can instead return an error and it crashes in another driver, it will become the problem of that other driver writer. Remember, when someone types '!analyze -v', it will blame the first non-Microsoft driver on the stack. Hence, it-s better to not be on the stack, at least from a support overhead burden.Validate ParametersSure, we all know that it isn't necessary to validate buffers that are passed to us by other kernel mode components, but only Microsoft has the luxury of this type of magic thinking. For the rest of us, it really is vital to keep in mind that the system is not static and we are constantly forced to 'figure out' why things are not working.Keep in mind when validating any information:.
Buffers are not static. You do not own the only reference to that buffer and they can (and do) change. If you want to rely upon the contents of buffers, you must capture that information (if it changes inside a private captured buffer, it's called 'memory corruption' and not a buffer handling error!).
With Windows Vista, MDLs that arrive in your filter can include 'dummy pages'. Autosim 200 crack serial idm. These pages can create potential confusion for any filter that tries to modify the data (e.g., an encryption filter) because that buffer's content is not static. This merely reinforces our observation before - if you want static content, capture it and operate on it separately from the original.If you decide to switch around the MDLs in an I/O request, don't forget to also update the UserBuffer field (the file systems rely upon this being 'correct' - which means it must be the same virtual address that is in the MDL passed).Beware the Failure CasesOver the years, we have consistently observed that developers are, as a group, remarkably optimistic. This shows in their coding, which tends to assume that everything is going to work properly. As it turns out, however, this optimism is unwarranted - and the source of frequent bugs. Further, as your driver is more widely deployed, you will observe a greater number of these failure scenarios.Indeed, in our experience, even mature code bases will experience 'we've never seen this failure case before' scenarios. Naturally, the fact that it is almost impossible to test all of these failure scenarios further complicates writing code to handle those cases.
![File System Filter Driver User Mode File System Filter Driver User Mode](/uploads/1/2/7/1/127164007/124518717.jpg)
Nevertheless, it is important for file system filter driver writers (both legacy and mini-filter writers) to think about error cases and handle them.In general, we've noticed a few classes of such failures:. Allocation failures. These are quite common, but something often overlooked is that handling allocation failures at the point you need the allocated object is frequently 'too late' to handle the error gracefully. Generally, we suggest moving the allocation to an earlier point in the code - keeping in mind that freeing an object won't fail. So, in a mini-filter (for example) we allocate our data structure for our context tracking information in the pre-create path, even though we won't use it until the post-create path. If we don't need it, we free it. In the pre-create path, however, we can gracefully return an error.
In post-create, we are forced to try and cancel the file open (which is far more complicated and subject to errors). Connection failures. While we'll mention redirectors 'later', we have noticed that timeouts and disconnections are common enough failures that just aren't handled. If you use a service, you have to consider what your driver will do when it fails, not to mention how it fails. Nothing is worse for a user than the frustration of having an operation (in your filter) hang forever waiting on a service that has entered an infinite loop. Unwinding failures. These are perhaps the most insidious of the error handling cases.
You've done your job and checked for a specific error case. But you have already done operations that need to be undone. Thus, your error handling code must then attempt to undo something you've already done. It could be as simple as changing the read-only bit on a file, or as complicated as changing its ACL. Some later step fails and you attempt to set it back - but that attempt to unwind itself fails.Of course, our point is not to provide some complete list, but rather to point out that when writing code, and particularly when code reviewing it, try looking through it and ignoring the 'everything works right' cases and look at the 'everything comes unraveled' cases. After all, the 'everything works right' case is by far the most likely case to be tested and fixed quickly.Avoid Changing File SizesThe most difficult filters to build and maintain are those that change the size of the underlying file and then try to mask this behavior from the applications. There are very good reasons for masking these changes from applications - after all, applications rely upon the size information in the directory entry being correct.
For example the 'verify' option for xcopy ('/v') actually looks at the size in the directory entry, not in the file itself.Exacerbating this issue is that directories can be surprisingly large (we've received bug reports against directories with over 700,000 entries in a single directory - so large that the owner of that large directory could not enumerate it with Explorer and instead only enumerated it from the command line!) If you have to actually open the file in order to retrieve the file size, it will make directory enumeration dramatically slower. In our experience, unless you implement caching, the cost of 'correcting directory entry sizes' has an amazing impact - it effectively makes doing almost anything with high access directories impossible (e.g., 'Windows').Underlying this is the fact that application programs are sloppy - they use information that is demonstrably not updated (NTFS, for example, only updates the size in the link used, so this size from the directory entry trick can be spoofed by using a link to change the file size).We have seen people try to use 'naming tricks' to resolve this issue. Fair warning: whenever you think you have a clever solution in a filter driver, ask yourself 'and how will this work when someone else implements a filter driver that plays the same/similar trick.' If the answer is 'it does not work' then you don't have a viable solution. Name prefix/suffix tricks fall into this category.The best thing to do is to never change size. If you must change the size of the file, the next best trick is to do this only on file systems that support both sparseness and alternate data streams - then you can keep a dummy default stream ('::$DATA' on NTFS) that is the correct size, with the modified data in an alternate data stream. Unfortunately, that often is not a viable solution because it is restricted to only some file systems.
In that case, be prepared for building a very complicated filter driver.FSD/MM/CC Interactions Are ComplicatedWe often observe that interactions in this business between the components are horribly complicated (See Figure 1). File system filter drivers sit in the midst of this complex environment and must be written to handle the ramifications of those interactions.Figure 1The simplest way to handle these interactions is to simply ignore them - in other words, don't get involved in the data flow through the system. That is definitely the simplest solution. Many filters must become involved in this in at least some fashion, however. Filters that are involved in I/O operations need to understand these interactions.Still, it is beyond the scope of this article to describe these complexities.
The point, however, is that in order to build a robust file system filter driver, you really do need to understand the file system.Redirectors Do Not Behave Like Local File SystemsSome filters can restrict themselves simply to a single file system (e.g. NTFS) or perhaps to a class of file systems (physical file systems) but many filters must handle the network as well. Over the years, the pattern we have observed is that filters are often written to target NTFS first and only once working against NTFS are they moved to other file system.Sadly, this approach fails horribly when it comes to the network file systems. Filters that 'work fine' on NTFS frequently exhibit serious problems when moved to the Windows redirector. Indeed, over the years, we've observed that it is often better to start with the network case and then move to NTFS - its generally a far less painful move than vice versa, particularly because project management often assumes that 'once it works on NTFS we're almost done.'
Some of the issues we have seen when it comes to filtering network redirectors over the years include:. On a local file system, it is rather easy to track unique instances of a given stream ('stream' is the NTFS terminology. On FAT, there is only a single stream within a file, but in NTFS there can be many streams within a single file). This is typically done using the FsContext parameter of the file object. Legacy filters may do this directly (via a lookup table keyed from this value) or indirectly (via a 'filter context' that is attached to a list within the FsContext structure). Mini-filters rely upon Filter Manager; Filter Manager in turn relies upon filter contexts. The Windows redirector, however, does not necessarily use the same context even for two file objects pointing to the same file.
This works correctly for them, but it violates this basic assumption in many filter drivers. Further complicating this, the network file systems may coalesce file objects together - changing their FsContext values when it detects two instances of the same file. Filters, once again, must be able to handle this particular case. Relative open 'issue'.
IoCreateFileSpecifyDevice ObjectHint (in at least some still in-use versions) has an issue when performing a relative open over the Windows redirector.
![](/uploads/1/2/7/1/127164007/110105058.jpg)