Filesystems play a key role in securing access to the files and directories they contain; in addition to storing the security information associated with each filesystem object, they must also enforce access restrictions based on that information. CBFS-based virtual filesystems, if desired, should generally handle security using one of the following options:
- For virtual filesystems which identify themselves (via the FileSystemName property) as NTFS, security should be handled using standard Windows security mechanisms: access control lists (ACLs).
- All other virtual filesystems should handle security, if desired, by implementing custom security checks.
Each security handling method is described in more detail below, followed by additional information relevant for both.
Windows Security Mechanisms (ACLs)
The CBFS class can optionally advertise support for NTFS security attributes by enabling the UseWindowsSecurity property (and, as implied above, this is highly recommended for virtual filesystems which identify as NTFS). When UseWindowsSecurity is enabled, the GetFileSecurity and SetFileSecurity events will fire anytime Windows needs to read or write security information. To correctly implement these events, applications must store and retrieve the OS-provided security information alongside the associated filesystem objects.
However, Windows doesn't handle the task of actually checking or enforcing access rights when sending requests to create or open filesystem objects, it's up to the virtual filesystem to do so. Therefore, when such requests arrive, the application must retrieve the previously-stored security information for the applicable file or directory, and then use that information to validate whether the process attempting access has sufficient rights to do so.
The aforementioned validation can be done easily using the Windows API's AccessCheck function, which validates a process's access rights against some object's security attributes; please refer to its documentation for more information. Once the application has performed the access validation, it should either allow or deny the request according to the results.
Custom Security Checks
The CBFS class also offers a number of methods that applications can use to help implement their own custom security checks. The two most notable of these methods are GetOriginatorProcessName, which returns the name of the process that initiated the request, and GetOriginatorToken, which returns the system-defined security token of the process that initiated the request.
The latter is particularly useful when used with various methods in the Windows API, such as the GetTokenInformation function, which can be used to obtain various pieces of information about the object the token is associated with.
Implementing Security Checks
Regardless of which security handling method is used, there are a number of things to keep in mind when implementing security checks in general. First and foremost, in order for an application to effectively enforce file security, it's important that the FireAllOpenCloseEvents property is enabled so that all file open requests are surfaced (and thus given a chance to be allowed or denied).
Secondly, consider that effective security enforcement does not require that all event handlers perform security checks. Applications are technically free to deny any file-related event due to a failed security check. However, it isn't actually meaningful to perform security checks in all event handlers in the first place, and unnecessary security checks will decrease an application's overall performance.
For example, it makes sense to validate access rights in the CreateFile and OpenFile event handlers, but not in the ReadFile or WriteFile event handlers. If an application denies a file create/open request made by some process, then that process won't be able to make a subsequent read/write request; therefore, applications can safely assume that all read/write requests come from processes whose access rights they have already verified. Similar logic can be applied for directories and directory enumerations. (Note that this is not an exhaustive set of use-cases; each application's needs will differ.)
Finally, as mentioned above, an application may allow or deny any operation based on security checks. However, it must not alter (or "selectively report") filesystem information based on the results of the validation process (it's "all or nothing"). Said another way: at any given time, and for each CBFS-based virtual filesystem associated with it, an application must expose a single, consistent representation of the filesystem's state to all processes accessing it. Here are some clarifying examples to help guide implementation:
- A filesystem object cannot be reported as existent to process A, but as nonexistent to process B.
- A filesystem object cannot be reported as a file to process A, but as a directory to process B.
- A filesystem object's metadata, absent of any changes, must be reported consistently between requests; e.g., if a file's size is reported as 1 KB to process A, then it must also be reported as 1 KB to process B.
- A file's contents, absent of any changes, must also be reported consistently between requests; e.g., for a file whose size is reported as 1 KB, the application must return exactly 1024 bytes when the file is read, and those 1024 bytes must be exactly the same (including ordering) regardless of which process is doing the reading.