Thursday, July 26, 2012

Testing Case-Sensitive Behavior

In this post I want to talk about case sensitivity and how to test such behavior for file systems and file system filters. I've addressed various case-sensitive file system filter specific topics in my previous posts on FILE_OBJECT Names in IRP_MJ_CREATE and on Names in Minifilters - Implementing Name Provider Callbacks.
This isn't a particular complicated subject but a developer must be careful in their implementation to support this concept. Other than figuring out the case sensitivity of the name in the IRP_MJ_CREATE path and in the name provider paths, a developer must in general pay attention to their implementation so that if, for example, they compare names or file extensions and they would use RtlEqualUnicodeString or RtlCompareUnicodeString then they must use the appropriate value for the CaseInSensitive parameter, deduced from the IRP_MJ_CREATE parameters or from the FILE_OBJECT flags. Also, when dealing with UNICODE_STRINGs, one should never try to change the case by directly touching the bits in the string, and instead always use functions like RtlUpcaseUnicodeString and RtlUpcaseUnicodeChar (and their counterparts, RtlDowncaseUnicodeString and RtlDowncaseUnicodeChar).
However, for this post I don't want to focus on implementation but on how to test this. In my opinion the best place to start is the IFS Test suite, since it has a lot of tests that deal with the create path, and in particular some that test both case sensitive behavior and case preserving behavior. However, when running this test on a regular system it will most likely fail with this message:
E94.E98 :  +TEST+SEV2      : Test         :CaseSensitiveTest
Group        :OpenCreateGeneral
Status       :C000001E (IFSTEST_TEST_NTAPI_FAILURE_CODE)
LastNtStatus     :C0000035 STATUS_OBJECT_NAME_COLLISION
ExpectedNtStatus :00000000 STATUS_SUCCESS
Description  :{Msg# OpCreatG!casesen!14} Failure while creating a
              test file \casesen\NeSesac.dat. Note that in a previous
              step we created another test file. That other test
              file \casesen\nesesac.dat, differs from this one by
              the case of the name. 
Lookup Query : http://www.microsoft.com/ContentRedirect.asp?prd=IFSKit&id=OpCreatG!casesen!14&pver=2195
This happens because by default Windows is case insensitive and so naturally the case sensitivity test fails. In order to enable case sensitive behavior for Windows all you need to do is change this registry value (it should be 1 be default so change it to 0):
HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\kernel\obcaseinsensitive
BTW, this is described in Microsoft's KB 817921, even though the case deals with something else entirely.
So anyway, once you set this key make sure that the file system itself is case sensitive and then test your filter like you normally would, though it might be a good idea to add a couple of tests with files that have the same name with different cases, just to make sure you're exercising the relevant code paths. For example, if implementing an anti-virus program it might be interesting to test with a clean file with the name "foo.txt" and the EICAR file "Foo.txt" in the same folder, to make sure that not only the scanning happens on the apropriate file, but also that the other parts of the program (such as the quarantine engine or the cleaning engine) work as expected. Another good test is to then switch file names (so that "Foo.txt" is clean and "foo.txt" is the EICAR file), so that in case the product somehow depends on the sorting order in the directory that issue can be found and addressed.

Finally, this is an easy way to check that the file system is case sensitive (UPDATE: as Phil pointed out in the comments, this doesn't mean that the requests are going to be case sensitive, it simply means the file system supports it; whether this is on or off generally depends on the file system implementation and not the registry key value):

C:\Windows\system32>fsutil fsinfo volumeinfo C:
Volume Name : System
Volume Serial Number : 0x4297004a
Max Component Length : 255
File System Name : NTFS
Supports Case-sensitive filenames
Preserves Case of filenames
Supports Unicode in filenames
Preserves & Enforces ACL's
Supports file-based Compression
Supports Disk Quotas
Supports Sparse files
Supports Reparse Points
Supports Object Identifiers
Supports Encrypted File System
Supports Named Streams
Supports Transactions
Supports Hard Links
Supports Extended Attributes
Supports Open By FileID
Supports USN Journal