Best Practices
You can use these best practices to create more efficient application filters.
Registration for Events
- Register for as small a set of events as possible. This is true for first-phase events registered in IFWXFilter::FilterInit and for events registered in IFWXFilter::AttachToSession. Unnecessary event registrations increase the load on the Microsoft Firewall service and reduce performance. Also, registration for unnecessary events may create a security risk, because your filter will not be designed to handle those events when they occur.
- In IFWXFilter::AttachToSession, do not define explicit data filters unless monitoring or modification of the stream is required. This allows the Firewall service to do the data pumping in kernel mode, which is more efficient than the user-mode data pumping of data filters.
Buffers and Data
- Copying data reduces performance, so avoid copying I/O buffer content. Instead, use the methods of the IFWXIOBuffer interface to manage data buffers. To avoid copying buffers, a filter should call IFWXSocket::Recv, specifying NULL as the buffer. This causes the Firewall service to pass to the filter an existing buffer, instead of copying from the existing buffer to the filter's buffer.
BSTR Strings
When working with BSTR strings, the implementing entity should be responsible for allocating and freeing memory. For example, if the Firewall service allocates the memory, it should also free it.
A likely scenario is the case where an application filter needs to use a BSTR held by the Firewall service. The Firewall service should provide a pointer to the BSTR, so that the application filter can allocate memory, copy the BSTR, and be responsible for freeing the memory. This approach reduces the likelihood of memory leaks.
Threading
If your filter performs asynchronous I/O on operating system files, use the completion notification mechanism provided by Forefront TMG. For more information, see Completion Notification Objects.
Call the IFWXFirewall::StartHeavyBlockingOperation method before making a blocking operation. When this method is used, the Firewall service keeps track of the number of threads that are busy with blocking operations, and creates new threads when necessary.
Do not call IFWXFirewall::StartHeavyBlockingOperation before waiting for a critical section or a resource on which other threads are likely to be blocked. If the newly created thread is blocked on the same critical section, there will be greater congestion on the critical section and more context switches, resulting in an overall reduction in performance.
Expect methods to be called from different threads simultaneously. If more than one thread accesses data at the same time, the data can be left in an inconsistent state. You can prevent this by using critical sections, which allow only one thread at a time to access the protected code section. The sample application filters provided with the Forefront TMG SDK contain examples of the use of critical sections. For more information about critical sections, see the reference page for the CCriticalSection object in the MSDN Library.
Similarly, you can use interlocked functions, which synchronize access to a variable that is shared by multiple threads. Interlocked functions prevent a thread from being preempted when it is in the middle of incrementing or checking a variable. For more information about critical sections, see Interlocked Variable Access in the MSDN Library.
In general, when calling a method of an object that may call back the calling object (either on the same thread or by waiting for another thread to perform the callback), a deadlock may arise. Therefore, avoid holding locks while calling methods of Firewall service implemented objects.
For example, there may be a race between calls to Detach (IFWXDataFilter::Detach or IFWXSessionFilter::Detach) and other methods. Data filters receive IFWXSocket interfaces when the IFWXDataFilter::SetSockets API is called. Because the Firewall service is multithreaded, more than one thread can use the same IFWXSocket interface simultaneously. Furthermore, one thread may call Detach to release an IFWXSocket interface, while the other thread still attempts to access the interface, perhaps while doing a data pump. Access to the interface should be protected by a locking mechanism, such as a critical section. Because of locking by the Firewall service, deadlock situations may arise. For example, the following code can lead to a deadlock situation:
Lock(); HRESULT hr = spSocket->Close(FALSE); Unlock();
To avoid the deadlock, use this code:
// Copy the member interface pointer to a local "smart" pointer // while the object is locked. CComPtr<IFWXSocket> spSocket; Lock(); spSocket = m_InternalSocket; Unlock(); // lock released to avoid deadlocks // Verify that interface was not released by Detach() if (spSocket != NULL) { // Here it is safe to send, receive, or close the socket. HRESULT hr = spSocket->Close(FALSE); }
Avoid creating too many threads in the Firewall service. If you create a thread that waits for an infrequent event, such as a configuration change, use the Windows synchronization function RegisterWaitForSingleObject. If you create a thread to perform periodic tasks that will spend most of its time sleeping, use the Windows function CreateTimerQueueTimer. Each of these functions uses thread pools to reduce the number of threads that are busy at one time.
Security for Secondary and Emulated Connections
There are situations where an application filter limits IP address access, including a:
- Secondary inbound connection that occurs when using FTP.
- Secondary or emulated connection in a publishing scenario.
For security purposes, the application filter defines a range of IP addresses for which a particular connection is allowed. Define that range of IP addresses by using the IFWXIpFilter interface.
Note If you set the range of IP addresses equal to NULL, you are allowing all addresses to connect.
Managing Application Filter Resource Usage
When no data filter is present, the Firewall service user-mode data pump controls resource usage by limiting the number of pending sends on a connection in either direction. For a given communication direction, buffers are received as long as there are less than two pending sends. If there are two pending sends, the receive is delayed until a send is completed. This prevents the accumulation of data buffers when the send rate does not match the receive rate.
When an application filter that uses a data filter is added to the data pump, the send-limiting mechanism does not operate. Your application filter should therefore check whether each send is completed, and should establish a limit as to how many sends can accumulate. When the limit has been reached, your filter should stop calling receive until the number of sends drops below the limit. If you do not create this type of resource usage control mechanism, a large download or a deliberate attack could cause buffers to accumulate, using extensive non-paged pool and user-mode memory. This could result in a denial of service.
Pending I/O Operations
During inbound or output emulation, a filter may build up an excessive number of pending I/O operations and overflow the Firewall service. A filter should impose a strict limit on the number of pending I/O operations that it allows.
Handling Data
Application filters should be designed not to allocate excessive amounts of memory per session or request so that they will not become vulnerable to a denial-of-service (DoS) attack. If filters accumulate data (for example, until there is a complete message), they should set a maximum message size. If this maximum message size is significant, we also recommend that they limit the time during which a message can be stored.
Application filters should never trust data. They should verify that the data is coming from the correct side. For example, they should verify that the client is not sending information that should be coming only from the server. Your filter should also verify that the data has the correct format.
When using hash tables, use salt and other mechanisms to make sure that clients will not be able to craft data that will always be sent to the same bucket.
Filter Setup
If an application filter also provides an extension for Forefront TMG Management, the setup program should allow for separate installation of the administration component and the filter component because Forefront TMG Management can be run remotely. In particular, Forefront TMG Management can be run from a remote computer on which the Forefront TMG services are not installed.
During filter registration, the error code HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS) should be considered as a success code, and installation should continue in a normal flow. This is done to avoid problems in re-registering an existing filter.
When an application filter is being uninstalled, the filter should try to uninstall all the objects that it registered (events, alerts, etc.), even if some of them no longer exist and an error code is returned. This is done to ensure that maximum cleanup is achieved, and to ensure forward compatibility with future support for array mode.
Writing to the Stored Configuration
Application filters should not attempt to write to the stored Forefront TMG configuration. Application filters are allowed read-only access to the stored Forefront TMG configuration. Therefore, an application filter that relies on successfully writing to the stored configuration will fail.
Any changes to the stored configuration that are needed for an application filter should be made by the setup process or through the application filter's administrative component.
Events and Alerts
An attacker can use an event or an alert to consume the resources of Forefront TMG computers by sending network traffic of a certain type. To mitigate the risk of denial-of-service (DoS) attacks, application filters should avoid signaling an event using the IFPCEventDefinition::Signal method for each occurrence of a network event that may occur many times in a short period of time. An application filter should maintain a record of the last time that each event was signaled and limit the total number of events that can be signaled in a short period of time (for example, to two or three times a second).
Similarly, an application filter should include a mechanism for preventing each alert that is issued by it from being issued more than once per second.
Send comments about this topic to Microsoft
Build date: 6/30/2010