Windows DLL 黑名单¶
概述¶
许多应用程序会与其他应用程序交互,这意味着它们会将其代码作为 DLL 在不同的进程中运行。例如,当防病毒软件尝试监控/阻止导航到恶意网站,或者屏幕阅读器尝试访问 UI 部分时,就会使用此技术。如果此类应用程序将其代码注入 Firefox,并且如果其在我们的 firefox.exe 中运行的代码存在 bug,即使它不是 Firefox 的 bug,也会表现为 Firefox 的 bug。
Firefox for Windows 具有一个功能,可以防止 DLL 加载到我们的进程中。如果我们知道某个特定的 DLL 会导致我们的进程出现问题,例如崩溃或性能下降,我们可以通过阻止 DLL 加载来解决此问题。
此黑名单针对的是在 Firefox 外部运行但与 Firefox 交互的第三方应用程序。对于附加组件,存在 不同的流程。
此页面说明了如何请求阻止您认为我们也应该阻止的 DLL,以及有关此功能的技术细节。
两种类型的黑名单¶
Firefox 中有两种类型的黑名单
编译到 Firefox 中的静态黑名单。它包含已知会导致 Firefox 出现问题的 DLL,用户无法禁用此黑名单。有关如何将新的 DLL 添加到此列表的更多信息和说明,请参阅下面 在静态黑名单中阻止 DLL 的流程。
用户可以使用动态黑名单来阻止导致他们出现问题的 DLL。此功能是在 bug 1744362 中添加的。
静态黑名单可以指定是否仅阻止 DLL 的某些版本,或者仅针对某些 Firefox 进程等。动态黑名单没有此功能;如果 DLL 在列表中,它将始终被阻止。
无论 DLL 在哪个黑名单中,如果它满足阻止条件,Firefox 都使用相同的机制来阻止它。在下面的 黑名单如何阻止 DLL 中有更多详细信息。
在静态黑名单中阻止 DLL 的流程¶
等等,我们真的应该阻止它吗?¶
使用静态黑名单阻止 DLL 应该是我们解决问题的最后手段,因为这样做通常会破坏安装 DLL 的应用程序的功能。如果有其他选择,我们应该始终选择它。有时,即使根本原因不在我们这边,我们也可以通过更改我们的代码来安全地绕过第三方的此问题。
当我们决定阻止它时,我们必须确保当前问题非常严重,以至于超过了用户选择安装软件、其提供的实用程序以及供应商自由分发和控制其软件的权重。
如何请求阻止 DLL¶
我们的代码库包含一个名为 WindowsDllBlocklistDefs.in 的文件,我们的构建过程会从中生成 DLL 黑名单作为 C++ 头文件并进行编译。要阻止新的 DLL,您需要创建一个补丁来更新 WindowsDllBlocklistDefs.in 并将其应用到我们的代码库,遵循我们的标准开发流程。此外,您需要填写特定于 DLL 阻止请求的表单,以便审阅者可以审查其影响和风险以及补丁本身。
以下是步骤:
如何编辑 WindowsDllBlocklistDefs.in¶
WindowsDllBlocklistDefs.in 将多个变量定义为 Python 数组。当您在黑名单中添加新条目时,您可以选择其中一个变量并以下面的语法添加条目:
语法¶
Variable += [
...
# One-liner comment including a bug number
EntryType(Name, Version, Flags),
...
]
参数¶
参数 |
值 |
---|---|
变量 |
ALL_PROCESSES | BROWSER_PROCESS | CHILD_PROCESSES | GMPLUGIN_PROCESSES | GPU_PROCESSES | SOCKET_PROCESSES | UTILITY_PROCESSES |
条目类型 |
DllBlocklistEntry | A11yBlocklistEntry | RedirectToNoOpEntryPoint |
名称 |
表示要阻止的 DLL 文件名的不区分大小写的字符串 |
版本 |
以下格式之一:
|
变量¶
选择以下预定义变量之一。
ALL_PROCESSES:此处定义的 DLL 在 BROWSER_PROCESS + CHILD_PROCESSES 中被阻止
BROWSER_PROCESS:此处定义的 DLL 在浏览器进程中被阻止
CHILD_PROCESSES:此处定义的 DLL 在非浏览器进程中被阻止
GMPLUGIN_PROCESSES:此处定义的 DLL 在 GMPlugin 进程中被阻止
GPU_PROCESSES:此处定义的 DLL 在 GPU 进程中被阻止
SOCKET_PROCESSES:此处定义的 DLL 在套接字进程中被阻止
UTILITY_PROCESSES:此处定义的 DLL 在实用程序进程中被阻止
条目类型¶
选择以下预定义条目类型之一。
DllBlocklistEntry:除非您的情况与其他条目类型匹配,否则使用此条目类型。
A11yBlocklistEntry:如果您只想在屏幕阅读器等辅助功能应用程序加载模块时阻止它,可以使用此条目类型。
RedirectToNoOpEntryPoint:如果模块是通过导入目录表注入的,则将模块添加为 DllBlocklistEntry 会中断进程启动,这意味着 DllBlocklistEntry 不是选项。您可以改为使用 RedirectToNoOpEntryPoint。
名称¶
表示要阻止的 DLL 文件名的不区分大小写的字符串。不要包含目录名称。
版本¶
要阻止的最大版本。如果指定了值,则会阻止具有指定版本、旧版本以及没有版本的模块。
要指定版本,您可以使用以下格式之一:
- 包含四个数字的元组。这将与作为版本资源嵌入 DLL 中的版本进行比较。示例:(1, 2, 3, 4)
- 表示 Unix 时间戳的 32 位整数,使用 PETimeStamp。这将与 IMAGE_FILE_HEADER::TimeDateStamp 的整数进行比较。示例:PETimeStamp(0x12345678)
技术细节¶
黑名单如何阻止 DLL¶
简单来说,如果给定的模块在黑名单中,我们会使 ntdll!NtMapViewOfSection 返回 STATUS_ACCESS_DENIED
,从而第三方代码,甚至 Firefox 的合法代码,尝试以任何方式(例如 LoadLibrary API)在我们的进程中加载 DLL 时都会失败并收到访问被拒绝的错误。
我们不应该阻止模块的情况¶
由于我们的黑名单的工作原理如上所述,因此存在我们不应该阻止模块的情况。
- 模块是通过 导入目录表 加载的阻止此类型的模块会阻止进程启动。您可以使用 RedirectToNoOpEntryPoint 来阻止此类型的模块。
- 模块作为 分层服务提供程序 加载在 Windows 8 或更高版本上阻止此类型的模块会导致网络连接中断。在 Windows 7 上阻止 LSP 是可以的。
(我们过去不得不避免阻止通过 窗口钩子 加载的模块,因为阻止此类型的模块会导致重复尝试加载模块,从而导致性能缓慢,例如 Bug 1633718,但这应该在 Bug 1823412 中得到修复。)
第三方模块 Ping¶
我们正在收集 第三方模块 Ping,它会捕获第三方模块加载到浏览器/选项卡/RDD 进程中的时刻。正如请求表单中所要求的那样,检查第三方模块 Ping 并查看我们想要阻止的模块是否出现在 Ping 中非常重要。如果它出现了,您可以通过查看 Ping 中的调用堆栈来了解模块是如何加载的。
如何在 Ping 中查看调用堆栈¶
您可以在 BigQuery 控制台或 STMO 上运行查询。(BigQuery 控制台速度更快,并且可以处理更大的数据。)
BigQuery 控制台(访问 此处 请求访问权限):https://console.cloud.google.com/bigquery
根据此模板创建您自己的查询。
运行查询。
将结果保存为 JSON 文件。
在 BigQuery 控制台中,点击 [保存结果] 并选择 [JSON(本地文件)]。
在 STMO 中,点击右上角的 […] 并选择 [显示 API 密钥],然后您可以从 [JSON 格式的结果] 中显示的 URL 下载 JSON 文件。
- (临时链接。需要找到永久位置。)
点击 [上传 JSON] 并选择您在步骤 4 中保存的文件。
点击表格中的一行以查看调用栈。
如何在 ping 中查看特定模块的版本¶
您可以使用此模板查询来查询 ping 中捕获的特定模块的哪些版本。这将显示正在积极使用的产品版本,包括崩溃版本和正常工作版本。
您还可以通过查询崩溃报告或 Socorro 表格来获取崩溃版本。拥有两个版本列表后,您可以决定是否可以在黑名单条目中指定 Version 参数。
初始化¶
为了最有效地阻止 DLL,黑名单在浏览器启动时非常早地进行初始化。如果启动器进程可用,则步骤如下:
启动器进程从磁盘加载动态黑名单(请参阅DynamicBlocklist::LoadFile())。
启动器进程将动态黑名单数据放入共享区域(请参阅SharedSection::AddBlocklist())。
启动器进程以挂起模式创建浏览器进程,设置其动态黑名单,然后启动它。(请参阅LauncherMain())。
这样做的目的是(理想情况下)在设置黑名单之前无法注入任何 DLL。
如果启动器进程不可用,则使用不同的黑名单,在mozglue/WindowsDllBlocklist.cpp中定义。此代码目前不支持动态黑名单。这仅用于测试和其他未部署的场景,因此对于用户来说不应构成问题。
请注意,mozglue 黑名单还具有一项功能,可以阻止在LoadLibrary
及其变体中启动的线程。此代码目前仅在 Nightly 版本中启用,因为它会破坏某些第三方 DLP 产品。
动态黑名单文件位置¶
由于黑名单在启动时加载得非常早,因此我们无法访问将要加载的配置文件,因此黑名单文件不能存储在那里。相反,默认情况下,黑名单文件存储在 Windows 用户的漫游应用程序数据目录中,具体来说是
<漫游 AppData 目录>\Mozilla\Firefox\blocklist-<安装 哈希>
请注意,此处的安装哈希是GetInstallHash()返回的内容,适合唯一标识正在运行的特定 Firefox 安装。
在首次启动时,此位置将写入注册表,并且可以通过将该键设置为不同的文件位置来覆盖。注册表键为HKEY_CURRENT_USER\Software\Mozilla\Firefox\Launcher
,名称为 firefox.exe 的完整路径,后跟“|Blocklist”。此代码位于LauncherRegistryInfo中。
向动态黑名单添加和删除条目¶
用户可以通过导航到about:third-party
,找到他们感兴趣的 DLL 的条目,然后点击破折号图标来向动态黑名单添加或删除 DLL。然后将提示他们重新启动浏览器,因为更改仅在浏览器重新启动后才会生效。
禁用动态黑名单¶
用户可能会通过将 DLL 放入动态黑名单而使 Firefox 进入不良状态。一种可能性是用户只阻止了一组交互的 DLL 中的一个,这可能会导致 Firefox 以不可预测的方式运行或崩溃。
通过使用--disableDynamicBlocklist
启动 Firefox,将加载动态黑名单,但不会将其用于阻止 DLL。这允许用户转到about:third-party
并尝试通过取消阻止或阻止 DLL 来解决问题。
类似地,在安全模式下,动态黑名单也会被禁用。
企业策略¶
可以通过在HKEY_CURRENT_USER\Software\Policies\Mozilla\Firefox
处设置注册表键来禁用动态黑名单,键名为 DisableThirdPartyModuleBlocking,DWORD 值为 1。这将导致不加载动态黑名单,并且在about:third-party
中不会显示任何图标以允许阻止 DLL。
联系方式¶
欢迎提出任何问题或反馈!
矩阵: #hardening