Thursday, September 22, 2011

Opening an Alternate Data Stream

Alternate Data Streams (ADS) are a pretty interesting feature of a filesystem, and one that can be quite useful for filter developers. ADS are an ideal way to store information related to a file, without interfering with operations on the main data stream. There are many reasons why such a feature is interesting to a file system filter developer so I'm not going to go into those. Instead I'd like to focus on one particular aspect of using them, opening the stream.

So why is opening an ADS such an interesting topic ? Because in almost all cases when a filter uses an ADS it wants to open it for a specific file that the user has open and is working on. Most implementations I've seen go about this by querying the name of the file that the user has opened ("\Device\HarddiskVolume1\test.bin") and then by appending the ADS name at the end of that name ("\Device\HarddiskVoluem1\test.bin:foo") and then by issuing an open for that. So what's wrong with this ? Well, as I've already said in one of my previous posts on names trying to open the same file that the user has open by name is problematic because the name of the file can change before the time the name is queried and the time the create reaches the file system (for example if there is a simple script that renames file a.bin to b.bin when a.bin exists and file b.bin to file a.bin when b.bin exists which in effect creates a scenario where a file very quickly changes name from a.bin to b. bin and back).

So I'd like to show a mechanism to open an ADS relative to an already open file. The local variable "openFileHandle" is a handle to the open file for which we want to open the ADS. Then the code would look something like this:


                    //
                    // this is the name of the ADS that we want to open the file.
                    //

                    UNICODE_STRING ADSName = RTL_CONSTANT_STRING(L":foo:$DATA");

		...

                    //
                    // initialize OBJECT_ATTRIBUTES with the handle we have and the name of the 
                    // ADS.
                    //

                    InitializeObjectAttributes( &objectAttributes,
                                                &ADSName,
                                                OBJ_KERNEL_HANDLE,
                                                openFileHandle,
                                                NULL );

                    //
                    // and now issue our open for the stream.
                    //

                    status = FltCreateFile( gFilterHandle,
                                            FltObjects->Instance,
                                            &ADSHandle,
                                            FILE_READ_DATA | FILE_READ_ATTRIBUTES,
                                            &objectAttributes,
                                            &ioStatus,
                                            0,
                                            FILE_ATTRIBUTE_NORMAL,
                                            FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                                            FILE_OPEN_IF, 
                                            FILE_OPEN_REPARSE_POINT,
                                            NULL,
                                            0,
                                            0 );

There are a couple of things worth mentioning about this approach:

  • Clearly this requires the main stream to be opened. So it can't be called from a preCreate for a file.
  • One cool feature is that it works regardless of whether openFileHandle is a handle to the main stream of a file or to another ADS. This is nice because normally building the name of the ADS to open when opening it by name requires special treatment for when the user has opened the ADS compared to when they have the main data stream.
  • Finally, please notice the FILE_OPEN_REPARSE_POINT flag. This is added because in case openFileHandle is a handle to a file that is a reparse point then our create will reparse which isn't what we want. So I put the FILE_OPEN_REPARSE_POINT flag just in case openFileHandle was opened with FILE_OPEN_REPARSE_POINT itself. The flag should have no effect otherwise.