Writing Stable Browser Extensions
The stability of Windows Internet Explorer is adversely affected by poorly implemented extensions such as Browser Helper Object (BHO), toolbars, or Microsoft ActiveX controls. This article summarizes important guidelines developers should follow when creating Component Object Model (COM) extensions to customize the browser and provides tips and best practices for well-behaved browser extensions that do not cause Internet Explorer to crash or become unresponsive ("hang").
This article contains the following sections:
- COM Considerations
- Tip 1: Use AddRef and Release correctly.
- Tip 2: Implement DllCanUnloadNow correctly and efficiently.
- Code Safety
- Tip 3: Always validate external input to prevent buffer overruns.
- Tip 4: Use the /GS compiler switch to add extra protection against buffer overruns.
- Tip 5: Check the return values of each function for errors before continuing.
- Tip 6: Handle your chosen threading model appropriately.
- Tip 7: Avoid single threaded controls.
- Tip 8: Create a worker thread to perform time-consuming tasks in the background.
- Tip 9: Provide a cancel option for long operations.
- Tip 10: Use the native Windows synchronization mechanisms.
- Tip 11: Never use TerminateThread.
- Tip 12: Do not use extremely long timeouts or INFINITE as a timeout value.
- Tip 13: Use the SMTO_ABORTIFHUNG flag.
- Tip 14: Use DisableThreadLibraryCalls to avoid new thread notifications.
- Tip 15: Create a separate routine for complex initialization.
- Memory Management
- Tip 16: Isolate the memory used by the extension in one heap.
- Tip 17: Enable memory protection to help mitigate online attacks.
- Exception Handling
- Tip 18: Always specify the exceptions you intend to catch.
- Tip 19: Provide a well-designed installation experience.
- Tip 20: Provide symbol (PDB) files for each external release.
- Tip 21: Sign up for the Windows Error Reporting system.
Tip 1: Use AddRef and Release correctly.
AddRef and Release control the life cycle of a COM object through reference counting; AddRef informs the COM object that one piece of code is now using the object and Release tells the COM object that one piece of code has finished using the object. While the reference count is greater than zero, the object remains active. When the reference count drops to zero, the object destroys itself to free memory.
Release must be called once and only once for each call to AddRef. If AddRef and Release are not used carefully, COM objects can destroy themselves early and cause a crash (too many calls to Release), or negatively impact performance by occupying memory until the browser closes (too many calls to AddRef). For more information, see Managing Object Lifetimes through Reference Counting.
Tip 2: Implement DllCanUnloadNow correctly and efficiently.
Every DLL that contains COM objects must implement DllCanUnloadNow, which is called by COM to see if the DLL can be unloaded. An incorrect or inefficient implementation of DllCanUnloadNow can cause crashes, hangs, or drops in performance.
DllCanUnloadNow should return S_FALSE immediately if the DLL cannot be unloaded. If the DLL can be unloaded, make sure all active threads created by the DLL have been terminated, any memory allocated by the DLL has been freed, and all user interface hooks (Window, mouse, or keyboard hooks) have been removed. Call CoUninitialize once for each CoInitialize that has been called by the DLL. Lastly, because COM makes no guarantees about when a DLL can be unloaded, the DLL must be fully ready to unload before DllCanUnloadNow returns S_OK. For more information, refer to the DllCanUnloadNow documentation.
Tip 3: Always validate external input to prevent buffer overruns.
One of the most common security and stability errors is the buffer overrun or buffer overflow, which happens when code writes more information to a memory buffer than it was designed to hold. Most buffer overruns are caused by bad input—writing too much data into a memory space so that it overwrites adjacent memory. Although this error commonly results in a program crash, malicious attackers can exploit this vulnerability to take control of the system. Internet Explorer extension developers should take extra precautions to avoid buffer overruns. For more information, see Avoiding Buffer Overruns.
Tip 4: Use the /GS compiler switch to add extra protection against buffer overruns.
Microsoft Visual C++ developers can use the /GS compiler option to detect buffer overruns. This switch introduces a new stack layout that includes a computed security cookie between the buffer and the return address of the function. If this value is overwritten, the application will report the error and quit. For more information, see Compiler Security Checks In Depth.
Tip 5: Check the return values of each function for errors before continuing.
When calling functions, developers should take care to check the validity of parameters they are passing. The parameters should be allocated properly, of the correct type, and within the range of values the function expects. This can prevent function errors before they occur; however, not all errors can be anticipated. If an error would affect the execution of the rest of your routine, you should detect and handle it. Take special care when introducing new error checking code. It is a frequent error to forget to release resources in newly introduced error code paths. See also Exception Handling later in this article.
Tip 6: Handle your chosen threading model appropriately.
The apartment threading model is recommended for extensions, but whether the extension is apartment-modeled or free-threaded, it should follow the rules appropriate to its chosen model. Internet Explorer is multithreaded and will call a free-threaded extension on multiple threads, so use great care when writing a free-threaded extension. For more information, see Processes, Threads, and Apartments.
Tip 7: Avoid single threaded controls.
Since version 4.0, Internet Explorer requires that all ActiveX controls it hosts to be at least apartment-threaded. The browser has performance and stability problems (hangs) with single threaded objects. See How to troubleshoot ActiveX control crashes in Internet Explorer for more information.
Tip 8: Create a worker thread to perform time-consuming tasks in the background.
Whenever the main thread for the extension has to wait for an extended period, any user input or UI messages are ignored until the wait is over. You can avoid this delay by calling functions on a different thread than the main thread that handles the UI. Worker threads are especially important if the extension calls out of the browser process, or uses file or network IO functions, as these operations can create a noticeable delay for users.
Tip 9: Provide a cancel option for long operations.
For single threaded applications, developers should create a Cancel button for any extended activity (or any activity that has the possibility of becoming extended). Applications with separate UI and worker threads should allow the user to cancel any extended operations on the UI thread.
Tip 10: Use the native Windows synchronization mechanisms.
Microsoft Win32 provides a rich set of synchronization constructs (for example, Critical Sections or Semaphores) to prevent threads from accessing data simultaneously. Developers should not create their own synchronization objects and should not avoid synchronization in order to bolster performance. For more information, see About Synchronization.
Tip 11: Never use TerminateThread.
The Win32 TerminateThread function forcibly terminates threads, which runs the risk of orphaning held locks and leaking the thread's stack allocation, among other things. Unless you know exactly what the thread is doing, you run the risk of putting the system into an inconsistent state. (For more information, see TerminateThread.) Instead, use a "shutdown" synchronization event to signal when worker threads are to finish.
For the same reason, take special care when using ExitThread too. When a thread exits, it should release all locks and any resources it has acquired based on the program logic.
Tip 12: Do not use extremely long timeouts or INFINITE as a timeout value.
Whenever you call any function that specifies a timeout value, resist the temptation to wait forever. An infinite timeout value can cause the extension (and the browser itself) to appear to hang because user input cannot be processed until the function returns. Functions that cause a long wait without timing out should also be avoided.
Tip 13: Use the SMTO_ABORTIFHUNG flag.
The SendMessage function does not return until the message being sent has been fully processed. This can cause a long wait if the application is still processing a previous message, or if the application is busy and not processing messages at all. SendMessageTimeout with the
SMTO_ABORTIFHUNG flag returns without waiting for the timeout period to elapse if the receiving thread is not responding.
Tip 14: Use DisableThreadLibraryCalls to avoid new thread notifications.
All browser extensions are implemented as DLLs and consequently they export a DllMain function, which is called by Windows when the DLL is attached or detached from the browser process. Because Internet Explorer makes extensive use of multithreading, frequent
DLL_THREAD_DETACH notifications to DllMain can slow the overall performance of the extension and the browser process. If your extension does not require thread-level tracking, call DisableThreadLibraryCalls during the
DLL_PROCESS_ATTACH notification. For more information, see DllMain.
Tip 15: Create a separate routine for complex initialization.
There are limits to what you can do in a DLL entry point. It is possible to introduce deadlocks in DllMain by calling complex APIs, such as those found in Shell32.dll. This happens because the DllMain function runs while holding a lock for the OS loader, which is acquired every time a DLL is loaded. In principle, any API that can trigger a DLL load behind the scenes should be avoided inside DllMain. (Refer to "DllMain Restrictions" section of Mixed DLL Loading Problem for more information.) If complex initialization is needed, create a global initialization function for the DLL and require applications to call the initialization routine before calling any other routines in the DLL.
Tip 16: Isolate the memory used by the extension in one heap.
Whenever possible, Internet Explorer extensions should use the Win32 HeapCreate function to allocate memory instead of using the default memory allocation provided by the compiler (malloc, for example). Do not use GetProcessHeap in conjunction with HeapAlloc; instead, use HeapCreate to allocate a heap dedicated to the extension's memory needs.
Tip 17: Enable memory protection to help mitigate online attacks.
Windows Internet Explorer 7 on Windows Vista introduced an off-by-default Internet Control Panel option to Enable memory protection to help mitigate online attacks. This option is also referred to as Data Execution Prevention or No-Execute (DEP/NX). The option is enabled by default in Windows Internet Explorer 8 on Windows Server 2008 and Windows Vista with Service Pack 1 (SP1).
DEP/NX helps to frustrate attacks by refusing to run code that is marked non-executable in memory. DEP/NX, combined with Address Space Layout Randomization (ASLR), makes it harder for attackers to exploit memory-related vulnerabilities like buffer overruns. Best of all, the protection applies to both Windows Internet Explorer and the add-ons it loads.
For Internet Explorer 7, DEP/NX was disabled by default for compatibility because some popular add-ons had been built with an older version of Active Template Library (ATL). Before version 7.1 SP1, ATL relied upon dynamically generated code in a way that was not compatible with DEP/NX. Fortunately, new DEP/NX APIs have been added to Windows Server 2008 and recent Windows Service Packs to enable use of DEP/NX while retaining compatibility with older versions of ATL.
If you build Internet Explorer add-ons, you can help ensure users enjoy a smooth upgrade to Internet Explorer 8 by taking the following steps:
- If your code depends on older versions of ATL, rebuild it with ATL v7.1 SP1 or later (Microsoft Visual Studio 2005 includes Active Template Library (ATL) 8).
- Set the
/NXCompatlinker option to indicate that your extension is compatible with DEP/NX.
- Test your code with DEP/NX enabled by using Internet Explorer 8 on Windows Vista with SP1, or Internet Explorer 7 after enabling the DEP/NX option. (To enable the DEP/NX option, run Internet Explorer as an administrator, and then set the appropriate checkbox in the Advanced tab of Internet Options.)
- Use other available compiler options like stack defense (
/GS), safe exception handling (
/SafeSEH), and ASLR (
Tip 18: Always specify the exceptions you intend to catch.
Internet Explorer extensions that take advantage of Win32 Structured Exception Handling should use GetExceptionCode to check the exception type before executing the handler. It is a mistake to catch access violations (AVs) and stack overflow errors because it hides inconsistent state in the process. Catching AVs does not make the extension more robust–it just transforms a crash into data corruption.
After a __try statement, make sure the __except expression only returns TRUE for a limited number of exceptions that the extension can handle. Never execute an exception handler for all exceptions, and do not use the UnhandledExceptionFilter or SetUnhandledExceptionFilter functions. For more information, see Structured Exception Handling.
Tip 19: Provide a well-designed installation experience.
If the Internet Explorer extension is collected into a package for distribution to users, the install program should only install on versions of the operating system that have been tested, and refuse to install on those that are unsupported. Additionally, the install program for the extension should also completely uninstall the extension's files.
Tip 20: Provide symbol (PDB) files for each external release.
If you are uncomfortable with sharing complete private debugging information, you might consider offering public ("stripped") symbols instead.
Tip 21: Sign up for the Windows Error Reporting system.
Windows Error Reporting (WER) is a set of Windows technologies that capture software crash data and support end-user reporting of crash information. Through Winqual services, software and hardware vendors can access reports in order to analyze and respond to these problems. WER technologies are implemented in Windows XP and Windows Server 2003 operating systems. For more information, see Windows Error Reporting: Getting Started.