Selective Encryption with CBFS Filter
Encrypting specific files within a designated directory on a Windows filesystem is a common task employed across various scenarios. This process safeguards sensitive information from unauthorized access, ensuring that only authorized users or applications can interact with the data. While encrypting files for the exclusive use of a single application may seem straightforward, the complexity increases when multiple applications require different access levels, ranging from read-only to full modification rights.
In this article, we will explore how CBFS Filter, particularly its CBFilter component, can facilitate per-process or per-user encryption and decryption of files.
Approaches To Encryption
Encryption and decryption of files (hereafter referred to as "encryption") can be as simple as transforming individual bytes, such as using the XOR operation, when a file is read or written. However, this basic approach has limitations: data are encrypted and decrypted for any process, and the only way to separate processes is to selectively permit and deny access to a file. Additionally, changing the file size—whether due to block encryption or the insertion of a header—can complicate handling and lead to compatibility issues with other software.
A more advanced approach to encryption involves separating the operations of each process from the actual storage and access of the file in the filesystem. This isolation provides each process with its own view of the file.
This article focuses solely on this second approach, which offers greater flexibility and security. By separating processes from the file, applications can present different views of the data. For instance, they can decrypt data for authorized applications while returning encrypted data to others, such as file managers or backup tools.
The article will use the Isolated Encryption sample as its foundation (view the GitHub Sample online). You can follow the sample rather than this article when implementing the encryption; the article provides the explanations of how the sample is implemented and why certain design decisions were made.
Basics of In-Place File Modification
In-place file modification through isolation—which includes the encryption we are discussing—relies on several interconnected technologies: file isolation, virtual files, and direct file access.
File isolation occurs when the filter driver modifies the original request and sends a secondary request with the modified data down the filter stack to the filesystem. When the driver receives a response to this second request, it completes the initial request using the information obtained from the second one.
Virtual files come into play when the filter driver handles a file-related request internally instead of sending it further down the filter stack. This may involve communicating with another data source or generating data within the driver itself. In our context, this data source is the application that processes events (callbacks from the driver).
Encryption is implemented in the application by handling events and accessing the same file for which the request was made, using isolation and direct file access.
While the diagrams may appear complex, the actual implementation is relatively straightforward:
- The application handles file open requests and, if necessary, informs the driver that the file is isolated.
- The application processes events related to the file (such as reading, writing, size changes, and closing). This involves transforming the data during read and write operations, then sending direct requests to the driver and the filesystem.
Architectural Considerations
For simplicity, we assume a directory where all files are automatically encrypted and decrypted. This configuration simplifies interactions with applications that save files as new entries with custom extensions before renaming the existing file. This is the mechanism most applications, including MS Office, use to save modified files.
In a real-world scenario, if you plan to encrypt only specific file types, it's essential to understand how applications handle file saving. If they initially write a modified file as a new file with a different extension, two problems arise:
- The data remains unprotected until the files are renamed, making it vulnerable to capture by a filesystem filter driver. Additionally, if the system crashes at this stage, the temporary file may persist on the drive.
- Your solution would need to encrypt the entire file at once when it gets renamed, which can be slow and complicate the handling of certain request sequences.
At the same time, if a third-party application writes a temporary file outside of your protected directory—such as in a temporary directory—there is little your solution can do unless it is aware of that application's behavior.
Another crucial consideration is the format of the encrypted data stored on disk. In most cases, you'll want to include some access-related information (such as session keys for encryption and access control lists) within the file, ideally in the header. Additionally, your implementation must support random access, as a process may need to read and write data at any position within a file, including beyond its end. This necessitates that an encryption scheme supports encryption and decryption of individual (even if large) blocks within a file.
Conditional isolation
Your application may choose to decode data exclusively for specific applications. This approach is perfectly valid; isolation can be enabled for certain file-opening requests while allowing other requests to proceed with default handling.
In the simplest scenarios, where only one or two processes are permitted to access the decoded data, it makes sense to establish specific rules for these processes (more on this below). In more complex situations, when an application needs to evaluate certain factors explicitly, this should be handled within event handlers.
CBFilter provides additional information about requests that is available to event handlers. This information includes:
- The Id of the thread that initiated the request (for file creation and opening - the thread that opens the handle)
- The name of the process that initiated the request (for file creation and opening - the process that opens the handle)
- The security token of the process that initiated the request (for file creation and opening - the process that opens the handle)
- The time when the request reached the filter
- The information about remote access if a local file is accessed over the network.
Setting Rules
When an application intends to encrypt the data in some directory, it needs to set up several filter rules and handle a dozen of events. The use of each event will be explained further down.
The events to handle are:
- BeforeCreateFile
- BeforeOpenFile
- BeforeQueryFileInfo
- BeforeSetFileInfo
- BeforeGetFileSecurity
- BeforeSetFileSecurity
- BeforeReadFile
- BeforeWriteFile
- BeforeRenameOrMoveFile
- BeforeCleanupFile
- AfterEnumerateDirectory
- CleanupContext
The event handlers are added as follows:
filter.OnBeforeCreateFile += CBFSFltBeforeCreateFile;
filter.OnBeforeOpenFile += CBFSFltBeforeOpenFile;
filter.OnBeforeQueryFileInfo += CBFSFltBeforeQueryFileInfo;
filter.OnBeforeSetFileInfo += CBFSFltBeforeSetFileInfo;
filter.OnBeforeGetFileSecurity += CBFSFltBeforeGetFileSecurity;
filter.OnBeforeSetFileSecurity += CBFSFltBeforeSetFileSecurity;
filter.OnBeforeReadFile += CBFSFltBeforeReadFile;
filter.OnBeforeWriteFile += CBFSFltBeforeWriteFile;
filter.OnBeforeRenameOrMoveFile += CBFSFltBeforeRenameOrMoveFile;
filter.OnBeforeCleanupFile += CBFSFltBeforeCleanupFile;
filter.OnAfterEnumerateDirectory += CBFSFltAfterEnumerateDirectory;
filter.OnCleanupContext += CBFSFltCleanupContext;
Then, several rules must be added.
The first rule to set up is the one that will track when the directory is enumerated and will correct the returned information. We add it like this:
filter.AddFilterRule("c:\\path\\to\\directory",
Constants.ACCESS_NONE,
Constants.FS_CE_AFTER_ENUMERATE_DIRECTORY,
Constants.FS_NE_NONE);
The mask is just a directory full name, without a wildcard. This mask will match the directory itself but not its children.
The second rule will tell the driver to fire events related to files and directories:
filter.AddFilterRule("c:\\path\\to\\directory\\*.*",
Constants.ACCESS_NONE,
Constants.FS_CE_BEFORE_CREATE |
Constants.FS_CE_BEFORE_OPEN |
Constants.FS_CE_BEFORE_QUERY_FILE_INFO |
Constants.FS_CE_BEFORE_SET_FILE_INFO |
Constants.FS_CE_BEFORE_READ |
Constants.FS_CE_BEFORE_WRITE |
Constants.FS_CE_BEFORE_GET_SECURITY |
Constants.FS_CE_BEFORE_SET_SECURITY |
Constants.FS_CE_BEFORE_RENAME |
Constants.FS_CE_BEFORE_CLEANUP |
Constants.FS_CE_AFTER_ENUMERATE_DIRECTORY,
Constants.FS_NE_NONE
);
Note that there is no flag set for the CleanupContext event. This is because this event is fired automatically if there is a non-zeroed FileContext or HandleContext value associated with an opened file.
The rule is set for all files in the directory(the "*.*" mask for filenames), recursively. If the application needs to exclude any or all subdirectories from processing, it can add a passthrough rule like this:
filter.AddPassthroughRule("c:\\path\\to\\directory\\*\\*.*",
Constants.ACCESS_NONE,
Constants.FS_CE_ALL,
Constants.FS_NE_NONE);
In this call, the path covers all files in all subdirectories, but you can modify it as needed.
If you exclude subdirectories from processing, you can remove the FS_CE_AFTER_ENUMERATE_DIRECTORY flag from the rule and skip handling the AfterEnumerateDirectory event - it will not be needed.
If the application needs to encrypt/decrypt data only for certain processes, it can add a process-specific rule (or a few rules). This is done by specifying the process name or PID in the mask, before the path; please, refer to the CBFilter documentation for details.
Handling Events
Creation and opening events
Both files and directories are created using the same OS requests. Additionally, before a file or directory can be utilized (for reading, updating, etc.), it must be opened. As a result, an application will handle file creation and opening events for both files and directories. While encryption is applicable only to files, some processing is still necessary for directories. Thus, the second filter rule mentioned earlier applies to both files and directories.
When creating or opening a file or directory, neither the caller nor the operating system can guarantee that the requested entry will exist by the time the request reaches the filesystem. To address this uncertainty, the OS provides a single CreateFile API function that includes a CreationDisposition parameter, instructing the filesystem on how to handle situations where the file may or may not exist. In CBFilter, there are two distinct events: BeforeCreateFile and BeforeOpenFile.
The BeforeCreateFile event is triggered only when the specified CreationDisposition is CREATE_NEW (to create a new file and fail if it already exists) or FILE_SUPERSEDE (a kernel-mode flag directing the filesystem to replace the file data). Notably, BeforeCreateFile is fired after BeforeOpenFile in the latter case.
For encryption purposes, both events are handled similarly: a backend file is created or opened based on the parameters received in the event handler.
Important: Before a file or directory is opened, the caller does not know if an existing entry (if any) is a file or a directory. CBFilter provides a way to query the attributes (including the DIRECTORY attribute) of the entry before firing BeforeCreateFile and BeforeOpenFile, passing the existing attributes to the corresponding event handlers. The event handlers need this information to create or skip the creation of an encryption context. To enable attribute collection, you can either use the AlwaysRequestAttributesOnOpen configuration setting or request the attributes directly in the handler. For implementing encryption, the second option is preferable, which we will cover below.
When a file or directory is created or opened, the application has several responsibilities, including:
- Verifying the process's access rights to the requested file or directory (this includes checking system security rights and any custom constraints stored within the file itself).
- Opening direct file handles to determine whether the request is for a file or directory and to access the file's encrypted data.
- For files, setting up the encryption parameters for subsequent read and write operations.
The first step in the event handlers is to inform the driver that the handle should be opened in isolated mode. If you provide decrypted data only to select processes, this is the point to perform dynamic checks to determine whether the opening should be isolated. If it should not be, you can return from the event handling at this point, allowing default processing to continue.
To enable isolation, set the Isolate parameter of the event to true and leave the BackendFilename parameter empty:
e.Isolate = true;
e.BackendFilename = "";
This action instructs the component and driver to route all requests related to the opened file handle to the application via the events.
In the case of files, since we are isolating the process from the backend file, the filesystem will not be able to perform the necessary security checks to verify that the caller process has access to the file. This can be addressed in one of two ways:
- The event handler can call the GetOriginatorToken method of CBFilter to obtain a security token for the process, which can then be used with the AccessCheck Windows API function to verify access to the backend file or directory being opened.
- Alternatively, you can offload the security checks to the driver by enabling the CheckFileAccessInDirectIO configuration setting, which instructs the driver to retrieve the security token and verify access rights to the backend file when it is opened using the CreateFileDirect method described below.
In addition to checking security attributes, the application may need to perform other validations on the file, some of which may require information stored within the file itself. These custom checks are possible once the file data is accessible (more on this below).
At this point, unless the AlwaysRequestAttributesOnOpen configuration setting is enabled, the handler does not know anything about the entry. The handler has two options: it can either check the access rights, open direct handles, and use one of them for requesting the attributes of the entry, or it can open a direct handle, use it to check if the entry is a directory first, and then perform security and access checks only for files. The choice lies with the developer.
In most cases, the application doesn't need to handle opening of directories in any special way. Thus, it is reasonable to figure out whether the request is related to a file or to a directory.
First, inspect the parameters of the request. If the CreateDisposition parameter of the event indicates the intention to create the entry but not open an existing one, and if the Attributes parameter indicates the DIRECTORY attribute, then no further processing is required, and the event handler may return:
if ((e.CreateDisposition == Constants.FILE_DISPOSITION_CREATE_NEW) && ((e.Attributes & Constants.FILE_SYS_ATTR_DIRECTORY) == Constants.FILE_SYS_ATTR_DIRECTORY))
{
e.ProcessRequest = true;
return;
}
If the intention is to open an existing entry, two situations are possible. The caller may have specified the option that informs the filesystem about the intention to open a directory. To check this option, inspect the Options parameter of the event. If it includes the FILE_DIRECTORY_FILE flag (numeric value 1), then no further processing is required, and the event handler may return:
if ((e.Options & 1) != 0)
{
e.ProcessRequest = true;
return;
}
If the option is not specified, you must query file or directory information, which requires a direct handle as described below.
For any operation, including requesting attributes and reading or writing data, the application must open a direct handle to the backend file (the one containing encrypted data) or directory.
Two handles must be opened:
- Main Handle This handle is opened with the access rights and share mode requested by the process. It will be used as a "guard" and to query the attributes of the entry.
- Data Handle: This second handle is suitable for reading and writing data with a relaxed share mode that allows other processes to access the same file.
An advanced explanation of why two direct handles are necessary will be provided in a subsequent section.
These direct handles can be opened using the CreateFileHandle method, or you can use CreateFileHandleAsStream to obtain a stream object, which is easier to manage. For simplicity, we will refer to these as "handles," as stream objects encapsulate those handles.
Note that CreateFileHandleAsStream will not work for a directory. Since we do not yet know whether the entry is a file or directory, we should use CreateFileDirect for the first handle, which will then be used to request the attributes.
To open the first direct handle (main handle), use code like this:
// Use original input parameters to perform share access check and then hold access reference.
long handleVaule = filter.CreateFileDirect(fileName, /*Synchronize: */ false, desiredAccess, shareMode, creationDisposition, flagsAndAttributes, false, false);
mainHandle = new IntPtr(handleVaule);
The desiredAccess, shareMode, creationDisposition, and flagsAndAttributes values come from the event parameters. The Synchronize parameter is set to false because we don't "inject" file access; instead, we are opening a different file (from the kernel's perspective) that just happens to have the same name as the file the process is attempting to open.
The second direct handle (data handle) is opened as follows:
var handleVaule = filter.CreateFileDirect(fileName, false,
(int)(GENERIC_READ | GENERIC_WRITE),
(int)(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE) |
Constants.CBFILTER_IGNORE_SHARE_ACCESS_CHECK,
(int)OPEN_EXISTING,
(int)FILE_FLAG_BACKUP_SEMANTICS,
false, false);
dataHandle = new IntPtr(handleVaule);
Here, we request both read and write access to the file while allowing other applications to operate as needed by specifying the appropriate share flags. If the file is opened only for reading (which can be checked in the desiredAccess parameter of the event), there is no need to include the GENERIC_WRITE flag.
The CBFILTER_IGNORE_SHARE_ACCESS_CHECK flag requests the system not to enforce the restrictions imposed by other handles on the same file, although NTFS may still perform checks in certain cases.
One of the handles should be used to request file attributes to determine if the entry is a file or directory. It is reasonable to also request the file size with the same call. The event handler can call either the GetFileInformationByHandle function of the Windows API (easier) or the QueryFileInformationDirect method with the FileStandardInformation class (more flexible, as other classes can be used as well) to request size and attributes. If you intend to use CreateFileDirectAsStream to work with a backend file, then the first (main) direct handle should be opened with CreateFileDirect and used to request the attributes. If the entry is a file, you can then use CreateFileDirectAsStream to open the second direct handle (the data handle).
If the entry is a directory, the application should immediately close the direct handle(s) as they will not be needed for further operations, set the ProcessRequest parameter to true to tell the driver that default processing is needed, and return.
For files, the direct handles (or stream objects) will be carried through to other events until the file is closed. Therefore, you need a context structure or object to maintain the handles along with other encryption-related information. A reference to this context may be stored in the FileContext parameter. While the documentation states that the FileContext parameter is shared among all "regular" (non-direct) handles opened for a file, in this article we focus on file isolation. In the context of isolation, the FileContext parameter differs for each handle opened by a process.
When an existing file is opened, the application likely needs to read the encryption information from the header (if one is used) to utilize it for encrypting and decrypting file data. For this, the application can check the size of the file obtained earlier (if the file is empty, there is nothing to read), then use the data handle to read the data from the file using the ReadFile Windows API function or a higher-level wrapper. If the stream object was obtained from CreateFileDirectAsStream, this stream object can be used for reading.
As the event is processed, set the ProcessRequest parameter to false to inform the driver that no default processing is required.
Why two handles? (additional reading)
The primary challenge in managing the file handle's lifetime is that the operating system allows processes to close all handles to a file while still keeping the file open and usable for reading and writing. This is particularly evident when a memory-mapped file is created; the memory mapping remains functional even after all handles to that file are closed. A similar approach is used in file caching, where the cache manager may flush file data after the handle is closed, necessitating that the file itself remains open until the cache manager releases it.
The "main" direct handle mimics the handle that an application would typically open—it has the same access rights and share mode. However, these parameters could prevent the file from being opened by the same or different processes. To allow for these openings, we need to close the main direct handle as soon as the process using the file closes its handle. This is accomplished in the BeforeCleanupFile event handler.
But what happens if the cache manager or memory manager needs to read from or write to the file after we close the handle? This is where the second, data handle comes into play, as it can be used to fulfill requests from those managers. We cannot use the main direct handle for this purpose because keeping it open until the file is finally closed could hinder other processes that need to open the file.
A practical example of this is found in applications that save files, including Microsoft Office. These applications often write data to a temporary file and then rename it. In this process, the file is opened, written to, closed, and then immediately reopened for renaming. If the main handle still exists when the file is opened the second time, it will block the operation, causing the renaming to fail.
Thus, we use the main direct handle solely as a guard to request the attributes during opening and to control other openings of the file. All file operations are conducted using the data direct handle.
It is essential to consistently use the same handle for all direct file operations and not mix the main and data handles; thus the second direct handle (data handle) should be used as its life span is longer.
File Operations
Typical file operations that we need to address include reading, writing, and changing the size of files. The system also queries the current parameters of the file.
As a result, we handle four corresponding events:
- BeforeQueryFileInfo
- BeforeSetFileInfo
- BeforeReadFile
- BeforeWriteFile
When managing file operations, it is important to check whether the FileContext parameter contains a reference to the previously initialized context. If we skipped opening the file during the BeforeCreateFile or BeforeOpenFile event, the context will not exist, and there is no need to process this event.
Query and set information
BeforeQueryFileInfo is triggered to query information about the file. It is important to note that different types of information can be requested, and the type and size of the returned data are determined by the InformationClass parameter of the event.
Since we have a backend file to work with, we first query the information from that file. This is accomplished by calling the QueryFileInformationDirect method using the data handle. This method sends the request to the filesystem and retrieves all requested information.
Once we have the information, we need to address the sizes. As mentioned earlier, your application will likely require a header at the beginning of the file, and the size of the encrypted data may be padded to the block size or page size of the encryption scheme, resulting in a footer.
Thus, we need to handle the FileStandardInformation and FileAllInformation classes, as their corresponding structures contain fields for size that require adjustment. These fields should be modified to exclude the header and footer (encryption padding) from the total size. Note that the allocation size typically represents the "size on disk," which is usually larger than or equal to the file size, though it may be smaller on a compressed drive.
We do not concern ourselves with the FilePositionInformation class because the driver manages the file position for all involved file handles.
BeforeSetFileInfo is triggered when the file size is changed and is handled similarly: you need to adjust the sizes in the relevant classes and then send the request to the backend file for processing using the SetFileInformationDirect method with the data handle. Note that writing data to a file does not trigger this event.
After handling each event, set the ProcessRequest parameter to false.
Reading of data
When handling data reading, we first check the Direction parameter, which indicates the flow of data between parties. The data may flow between the file, the cache, and the application. As isolation ensures a private cache for the process reading or writing the data, it is reasonable to decrypt the data once when they go from the filesystem to the cache or the process (which happens when the process opens a file in non-buffered mode) and skip the data flow from the cache to the process.
Thus, if the reading is non-cached (i.e., the data is moving from the file to the application or to the cache), we need to read the data from the backend file using the data handle, decrypt the retrieved data, and provide the decrypted information to the driver for delivery to the request originator. Remember to adjust the reading position passed to the event handler so that it aligns with the actual position of the data being read.
We want to avoid interfering with the reading of cached data by applications since the data in the cache is already decrypted. Therefore, we skip the corresponding direction.
Writing of data
Handling data writing is slightly more complex. An event handler should inspect the Direction parameter to determine, which actions it should take.
Once a process writes some data to a file, it often occurs that the size of the file is increased. Usually, the data is first written to the file cache. Here, it is the "frontend" file that the process is working with, but if your file in the filesystem contains a header or a footer (encryption padding), you need to increate the size of that file accordingly. This is required because even as the data is still in the cache, the amount of data in the file has increased. The process should be able to request current size of the file and receive it right, so that it could read the data back (from the cache so far) if necessary.
For example, if the process has written 16 bytes of data to an empty file (even as the data is present only in the cache) and your encrypted files have a 4096 byte header with no footer, the backend file should be resized to 5012 bytes. If the process now queries the size, your event handlers would be able to return "5012 - 4096 = 16" as the size. Then, when the cache flushes these 16 bytes of data, you will have space for this data in your backend file.
When data flows from the application or cache to the file, encrypt the data and write the encrypted information to the backend file using the data handle. Remember to adjust the writing position passed to the event handler to match the actual position where the the data should be written to.
In some cases, the data may be written by a process right into the file without caching (when the file is opened in non-buffered mode). The event handler should take care of this mode as well by both adjusting the file size (like when the data is written to the cache) and writing the data like when the data is flushed by the cache to the file.
Security-related events
An encryption application should manage the BeforeGetFileSecurity and BeforeSetFileSecurity events, typically by passing the corresponding requests to the actual filesystem. This is accomplished by calling the QueryFileSecurityDirect and SetFileSecurityDirect methods, while passing the event parameters to these methods as needed. Generally, no specific data processing is required in these events. The only additional step is to set the ProcessRequest parameter to false, indicating that the event handler has processed the request.
File renaming and moving
When a file is moved out of the directory, it may need to be decrypted, depending on the application design. Similarly, moving a file into the directory may require encryption. To manage this, the application handles the BeforeRenameOrMoveFile event and initiates the encryption or decryption process.
There are various approaches to this task: the file can be encrypted or decrypted synchronously in the event handler, or the procedure can be enqueued for asynchronous processing. The drawback of synchronous processing is that encryption or decryption can take a long time for large files, potentially blocking the operation and leading to timeouts or errors. We will discuss timeouts later in this article.
To rename or move a backend file, the application can use the SetFileInformationDirect method with the FileRenameInformation class. Renaming the file using high-level functions that accept a filename will not work because open handles to the file will prevent the renaming. Alternatively, the application can take a more roundabout approach: it can close the direct handles, use a high-level renaming function, and then reopen the direct handles. However, this method carries the risk that another application may open the file, which could cause the reopening of the direct handles to fail.
Closing the handles
The final essential step is closing the direct handles.
The main direct handle must be closed in the BeforeCleanupFile event handler to prevent it from interfering with further file operations. The handle obtained from CreateFileDirect should be closed using the system function CloseHandle. If you used CreateFileDirectAsStream, you should close the corresponding stream instead.
The data handle can be closed in the CleanupContext event handler.
Handling directory enumeration
To ensure that a process receives consistent information about file sizes when enumerating a protected directory (or one of its subdirectories) and reading a file from that directory, it is advisable to handle the AfterEnumerateDirectory event and adjust the reported size of each file.
Directory enumeration occurs separately for each process (there is no shared cache of directory information among processes), allowing the handler to check the originator information to determine whether the calling process will see encrypted or decrypted data if it opens the file. However, doing this for each entry can be resource-intensive. An alternative approach is to handle the opening of the directory, assess whether the calling process may access decrypted data, and store the result in the HandleContext parameter. Then, when processing AfterEnumerateDirectory, the handler can inspect the value in HandleContext and respond accordingly. However, this method may not work if a process has different rights for different files within the same directory.
Another important consideration is that opening each file to determine its real (decrypted) data size during directory enumeration would be very time-consuming. Employing caching strategies to store size information is essential. The simplest solution is to maintain a list of decrypted file sizes in memory. This list can be populated during the first directory enumeration (which may be slow) and updated whenever a file is modified or a new file is added to the directory. Additionally, this list can be made persistent (saved in the same directory or elsewhere) so that it can be reused after a system restart.
Event timeouts
To prevent event handlers from becoming stuck and blocking the system, CBFilter implements a timeout mechanism. The desired timeout is specified as a parameter when filtering is initiated by an application (in a call to the StartFilter method). When the driver invokes a user-mode call, it starts a timer and expects the call to complete within the predefined timeout. If the call does not complete in time, execution continues, and the call is treated as abandoned—any returned data (if any) will be ignored.
The only exceptions are the BeforeCreateFile and BeforeOpenFile events; if a call to the handler times out, the file will not be created or opened, serving as a security measure.
The encryption application must address timeouts when encrypting or decrypting complete files (typically when a file is moved into or out of the directory). One approach to handle long operations is to enqueue them for later asynchronous processing. However, asynchronous processing introduces its own challenges. Alternatively, you can accurately measure the time spent in the event handler, and if it approaches the limit, call the ResetTimeout method to reset or disable the timer for that specific event. Keep in mind that doing this doesn't allow the operation to continue uninterrupted — a slow operation will still block the process performing it and may, in some situations, block other operations on the same filesystem.
Getting Started with CBFilter
You can find an evaluation version of the SDK for your platform and programming language in the Download Center. After downloading and installing the SDK, you will receive a library, sample code, and comprehensive documentation on your system. The documentation includes a "Getting Started" section with programming instructions. Additionally, free technical support is available during the evaluation phase.
We appreciate your feedback. If you have any questions, comments, or suggestions about this article please contact our support team at support@callback.com.