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

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 ISA Server 2004 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 on 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:

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 vulnerabale 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 ISA Server
Management, the setup program should allow for separate
installation of the administration component and the filter
component because ISA Server Management can be run remotely. In
particular, ISA Server Management can be run from a remote computer
on which ISA Server 2004 is 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
ISA Server configuration. Application filters are allowed read-only
access to the stored ISA Server configuration on computers running
Windows Server 2003. 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.