Windows DLL Blocklisting
Overview
There are many applications which interact with another application, which means they run their code as a DLL in a different process. This technique is used, for example, when an antivirus software tries to monitor/block navigation to a malicious website, or a screen reader tries to access UI parts. If such an application injects their code into Firefox, and if there is a bug in their code running in our firefox.exe, it will emerge as Firefox’s bug even though it’s not.
Firefox for Windows has a feature to prevent DLLs from being loaded into our processes. If we are aware that a particular DLL causes a problem in our processes such as a crash or performance degradation, we can stop the problem by blocking the DLL from being loaded.
This blocklist is about a third-party application which runs outside Firefox but interacts with Firefox. For add-ons, there is a different process.
This page explains how to request to block a DLL which you think we should block it as well as technical details about the feature.
Two types of blocklists
There are two types of blocklists in Firefox:
A static blocklist that is compiled in to Firefox. This consists of DLLs known to cause problems with Firefox, and this blocklist cannot be disabled by the user. For more information and instructions on how to add a new DLL to this list, see Process for blocking a DLL in the static blocklist below.
A dynamic blocklist that users can use to block DLLs that are giving them problems. This was added in bug 1744362.
The static blocklist has ways to specify if only certain versions of a DLL should be blocked, or only for certain Firefox processes, etc. The dynamic blocklist does not have this capability; if a DLL is on the list it will always be blocked.
Regardless of which blocklist the DLL is on, if it meets the criteria for being blocked Firefox uses the same mechanism to block it. There are more details below in How the blocklist blocks a DLL.
Process for blocking a DLL in the static blocklist
But wait, should we really block it?
Blocking a DLL with the static blocklist should be our last resort to fix a problem because doing it normally breaks functionality of an application which installed the DLL. If there is another option, we should always go for it. Sometimes we can safely bypass a third-party’s problem by changing our code even though its root cause is not on our side.
When we decide to block it, we must be certain that the issue at hand is so great that it outweighs the user’s choice to install the software, the utility it provides, and the vendor’s freedom to distribute and control their software.
How to request to block a DLL
Our codebase has the file named WindowsDllBlocklistDefs.in from which our build process generates DLL blocklists as C++ header files and compiles them. To block a new DLL, you create a patch to update WindowsDllBlocklistDefs.in and land it on our codebase, following our standard development process. Moreover, you need to fill out a form specific to the DLL blockling request so that reviewers can review the impact and risk as well as the patch itself.
Here are the steps:
File a bug if it does not exist.
Answer all the questions in this questionnaire, and attach it to the bug as a plaintext.
Make a patch and start a code review via Phabricator as usual.
How to edit WindowsDllBlocklistDefs.in
WindowsDllBlocklistDefs.in defines several variables as a Python Array. When you add a new entry in the blocklists, you pick one of the variables and add an entry in the following syntax:
Syntax
Variable += [
...
# One-liner comment including a bug number
EntryType(Name, Version, Flags),
...
]
Parameters
Parameter |
Value |
---|---|
Variable |
ALL_PROCESSES | BROWSER_PROCESS | CHILD_PROCESSES | GMPLUGIN_PROCESSES | GPU_PROCESSES | SOCKET_PROCESSES | UTILITY_PROCESSES |
EntryType |
DllBlocklistEntry | A11yBlocklistEntry | RedirectToNoOpEntryPoint |
Name |
A case-insensitive string representing a DLL’s filename to block |
Version |
One of the following formats:
|
Variable
Choose one of the following predefined variables.
ALL_PROCESSES: DLLs defined here are blocked in BROWSER_PROCESS + CHILD_PROCESSES
BROWSER_PROCESS: DLLs defined here are blocked in the browser process
CHILD_PROCESSES: DLLs defined here are blocked in non-browser processes
GMPLUGIN_PROCESSES: DLLs defined here are blocked in GMPlugin processes
GPU_PROCESSES: DLLs defined here are blocked in GPU processes
SOCKET_PROCESSES: DLLs defined here are blocked in socket processes
UTILITY_PROCESSES: DLLs defined here are blocked in utility processes
EntryType
Choose one of the following predefined EntryTypes.
DllBlocklistEntry: Use this EntryType unless your case matches the other EntryTypes.
A11yBlocklistEntry: If you want to block a module only when it’s loaded by an accessibility application such as a screen reader, you can use this EntryType.
RedirectToNoOpEntryPoint: If a modules is injected via Import Directory Table, adding the module as DllBlocklistEntry breaks process launch, meaning DllBlocklistEntry is not an option. You can use RedirectToNoOpEntryPoint instead.
Name
A case-insensitive string representing a DLL’s filename to block. Don’t include a directory name.
Version
A maximum version to be blocked. If you specify a value, a module with the specified version, older versions, and a module with no version are blocked.
To specify a version, you can use either of the following formats:
- A tuple consisting of four digits. This is compared to the version that is embedded in a DLL as a version resource.Example: (1, 2, 3, 4)
- A 32-bit integer representing a Unix timestamp with PETimeStamp. This is compared to an integer of IMAGE_FILE_HEADER::TimeDateStamp.Example: PETimeStamp(0x12345678)
Technical details
How the blocklist blocks a DLL
Briefly speaking, we make ntdll!NtMapViewOfSection return
STATUS_ACCESS_DENIED
if a given module is on the blocklist, thereby a
third-party’s code, or even Firefox’s legitimate code, which tries to load a DLL
in our processes in any way such as LoadLibrary API fails and receives an
access-denied error.
Cases where we should not block a module
As our blocklist works as explained above, there are the cases where we should not block a module.
- A module is loaded via Import Directory TableBlocking this type of module blocks even a process from launching. You may be able to block this type of module with RedirectToNoOpEntryPoint.
- A module is loaded as a Layered Service ProviderBlocking this type of module on Windows 8 or newer breaks networking. Blocking a LSP on Windows 7 is ok.
(we used to have to avoid blocking modules loaded via a Window hook because blocking this type of module would cause repetitive attempts to load a module, resulting in slow performance like Bug 1633718, but this should be fixed as of Bug 1823412.)
Third-party-module ping
We’re collecting the third-party-module ping which captures a moment when a third-party module is loaded into the Browser/Tab/RDD process. As it’s asked in the request form, it’s important to check the third-party-module ping and see whether a module we want to block appears in the ping or not. If it appears, you may be able to know how a module is loaded by looking at a callstack in the ping.
How to view callstacks in the ping
You can run a query on BigQuery console or STMO. (BigQuery console is much faster and can handle larger data.)
BigQuery console (visit here to request access): https://console.cloud.google.com/bigquery
Make your own query based on this template.
Run the query.
Save the result as a JSON file.
In BigQuery console, click [SAVE RESULTS] and choose [JSON (local file)].
In STMO, click […] at the right-top corner and select [Show API Key], then you can download a JSON from a URL shown in the [Results in JSON format].
- (A temporal link. Need to find a permanent place.)
Click [Upload JSON] and select the file you saved at the step 4.
Click a row in the table to view a callstack
How to see the versions of a specific module in the ping
You can use this template query to query which versions of a specific module are captured in the ping. This tells the product versions which are actively used including the crashing versions and the working versions.
You can also get the crashing versions by querying the crash reports or the Socorro table. Having two version lists, you can decide whether you can specify the Version parameter in a blocklist entry.
Initialization
In order to have the most effective blocking of DLLs, the blocklist is initialized very early during browser startup. If the launcher process is available, the steps are:
Launcher process loads dynamic blocklist from disk (see DynamicBlocklist::LoadFile())
Launcher process puts dynamic blocklist data in shared section (see SharedSection::AddBlocklist())
Launcher process creates the browser process in a suspended mode, sets up its dynamic blocklist, then starts it. (see LauncherMain())
This is so (ideally) no DLLs can be injected before the blocklist is set up.
If the launcher process is not available, a different blocklist is used, defined in mozglue/WindowsDllBlocklist.cpp. This code does not currently support the dynamic blocklist. This is intended to only be used in testing and other non-deployed scenarios, so this shouldn’t be a problem for users.
Note that the mozglue blocklist also has a feature to block threads that start
in LoadLibrary
and variants. This code is currently only turned on in
Nightly builds because it breaks some third-party DLP products.
Dynamic blocklist file location
Because the blocklist is loaded so early during startup, we don’t have access to what profile is going to be loaded, so the blocklist file can’t be stored there. Instead, by default the blocklist file is stored in the Windows user’s roaming app data directory, specifically
<Roaming AppData directory>\Mozilla\Firefox\blocklist-<install hash>
Note that the install hash here is what is returned by GetInstallHash(), and is suitable for uniquely identifying the particular Firefox installation that is running.
On first launch, this location will be written to the registry, and can be
overriden by setting that key to a different file location. The registry key is
HKEY_CURRENT_USER\Software\Mozilla\Firefox\Launcher
, and the name is the
full path to firefox.exe with “|Blocklist” appended. This code is in
LauncherRegistryInfo.
Adding to and removing from the dynamic blocklist
Users can add or remove DLLs from the dynamic blocklist by navigating to
about:third-party
, finding the entry for the DLL they are interested in, and
clicking on the dash icon. They will then be prompted to restart the browser, as
the change will only take effect after the browser restarts.
Disabling the dynamic blocklist
It is possible that users can get Firefox into a bad state by putting a DLL on the dynamic blocklist. One possibility is that the user blocks only one of a set of DLLs that interact, which could make Firefox behave in unpredictable ways or crash.
By launching Firefox with --disableDynamicBlocklist
, the dynamic blocklist
will be loaded but not used to block DLLs. This lets the user go to
about:third-party
and attempt to fix the problem by unblocking or blocking
DLLs.
Similarly, in safe mode the dynamic blocklist is also disabled.
Enterprise policy
The dynamic blocklist can be disabled by setting a registry key at
HKEY_CURRENT_USER\Software\Policies\Mozilla\Firefox
with a name of
DisableThirdPartyModuleBlocking and a DWORD value of 1. This will have the
effect of not loading the dynamic blocklist, and no icons will show up in
about:third-party
to allow blocking DLLs.
Contact
Any questions or feedback are welcome!
Matrix: #hardening