Chromium
Chromium 主要包含两大核心组成部分:渲染引擎和浏览器内核。
Chromium 目前使用 Blink 作为渲染引擎,它是基于 webkit 定制而来的,核心逻辑位于项目仓库的third_party/blink/
目录下。渲染引擎做的事情主要有:
- 解析并构建 DOM 树。Blink 引擎会把 DOM 树转化成 C++ 表示的结构,以供 V8 操作
- 调用 V8 引擎处理 JavaScript 和 Web Assembly 代码,并对 HTML 文档做特定操作
- 处理 HTML 文档定义的 CSS 样式
- 调用 Chrome Compositor,将 HTML 对应的元素绘制出来。这个阶段会调用 OpenGL,未来还会支持 Vulkan。在 Windows 平台上,该阶段还会调用 DirectX 库处理;在处理过程中,OpenGL还会调用到 Skia,DirectX 还会调用到 ANGLE
Blink组件间的调用先后关系,几乎所有发生在浏览器页签中的工作,都有Blink参与处理。可用下图概括:
浏览器内核扮演连接渲染引擎及系统的“中间人”角色,具有一定“特权”,负责处理的事务包括但不限于:
- 管理收藏夹、cookies以及保存的密码等重要用户信息
- 负责处理网络通讯相关的事务
- 在渲染引擎和系统间起中间人的角色。渲染引擎通过Mojo与浏览器内核交互,包含组件:download、payments等等。
Chromium渲染引擎涉及大量C++编写的组件,出现漏洞的概率不小。因此,基于纵深防御理念浏览器引入了沙箱机制。渲染引擎等组件不直接与系统交互,而是通过一个被称为 MOJO 的 IPC 组件与浏览器引擎通讯(也被称为 broker),再与系统交互。进而可以实现:即便沙箱中的进程被攻破,但无法随意调用系统API产生更大的危害。
Sandbox
沙箱是一个 C++ library,沙箱进程便是通过该
C++ library
所创建,为了确保安全性,沙箱进程所处的执行环境是非常受限的。沙盒进程可以自由使用的唯一资源是 CPU 周期和内存。例如,沙箱进程不能向磁盘写数据或显示它们自己的窗口。
Chromium renderers 便是一个沙盒进程。沙箱的目标是提供关于一段代码最终能做什么或不能做什么的硬保证。
沙箱限制了在沙箱内运行代码的 bug 的影响性,例如:这种bug不能在用户帐户中安装持久性恶意软件(因为沙箱禁止写入文件系统)也不能从计算机本地当中读取任何文件。但是沙箱不能够对系统组件提供保护(如运行它的内核)
沙箱无法针对系统组件(例如运行它的内核)中的错误提供任何保护。
沙箱在运行初始时并不是完全状态,只有当进程调用
LowerToken()
方法后,沙箱才会完全生效。这样的设置,使得沙箱进程在启动这段时间内,可以自由的获取关键资源,加载 library,读取配置文件。所以,当进程开始和不受信任的文件交互之前,应尽快调用LowerToken()
。Note:如果进程被感染,在调用
LowerToken()
后,打开的操作系统句柄都可能会被恶意软件滥用。浏览器渲染引擎、GPU、PPAPI插件以及语音识别服务等进程是运行在沙箱中的。此外不同系统平台下的部分服务也会受沙箱保护,例如Windows下打印时调用的PDF转换服务、icon浏览服务;MacOS下NaCl loader、需要访问IOSurface的镜像服务等。
Windows Sandbox 架构
- Windows sandbox 是仅限于 user-mode的沙箱,也没有特殊的内核模式驱动程序,因此用户不需要成为管理员,以确保 Sandbox 的正常运行。
- Sandbox 有 32 位和 64 位两种,在所有的 Win7 和 Win10 均已经被测试
- Sandbox 在进程级粒度进行运作,任何需要沙箱化的目标,都需要其是独立进程。最简单的沙箱配置需要两个进程:一个是被称为 broker 的 privileged controller,以及被称为 target 的一个或多个沙箱化进程。
- Sandbox 作为静态库提供,必须链接到 broker 和目标可执行文件。
The broker process
在 chromium 中,broker就是浏览器的主进程。宽泛的说,borker 是权限控制器 / sandbox进程活动的管理员。其职责是:
- 指定每个目标进程中的策略
- 生成目标进程
- 维护沙箱策略引擎服务
- 维护沙箱拦截管理器
- 维护沙箱IPC服务(与target进程的通信)
broker 的存活时间总是比他生成的目标进程要长,Sandbox IPC是一种用于将某些 Windows API 调用从 target 转发到 broker 的低级机制(不同于Chromium的IPC),这些 API 根据策略而定。策略允许的 API 则由 borker 进行调用,结果会通过同样的IPC返回给目标进程。 Interceptions manager 的工作给应该通过 IPC 转发给 broker 的Windows API调用提供补丁。
The target process
在 chromium 中,target process 就是 renderers,除非执行了 --no-sandbox
命令。target 进程维护所有将在沙箱中运行的代码,以及沙箱 client 方面的基础架构:
- 对所有代码进行沙箱化
- 沙箱化 IPC client
- 沙箱策略引擎客户端
- 沙箱拦截
2、3、4 是 sandbox library 的一部分,需要和被沙箱化的代码一同链接
Interceptions(hooks)是 Windows API 调用通过沙箱 IPC 转发到 broker 的方式。The interceptions (also known as hooks) are how Windows API calls are forwarded via the sandbox IPC to the broker.
Sandbox restrictions
沙盒依赖于 Windows 提供的四种保护机制:
A restricted token
The Windows job object
The Windows desktop object
Integrity levels
这些机制在保护操作系统,操作系统的限制,用户提供的数据上相当的高效,前提是:
- 所有可以安全化的资源都有一个比null更好的安全描述符。换言之,没有关键资源会有错误的安全配置。
- 电脑还没有被恶意软件破坏。
- 第三方软件不会降低系统的安全性。
The token
对于 Chromium Sandbox 最严格的 token 采用以下形式:
1 | Regular Groups |
Chromium 的渲染器以某个 token 运行,意味着渲染器进程使用的几乎所有资源都已被浏览器获取,它们的句柄也将被复制到渲染器的进程中。
NOTE:token 不是派生于anonymous or guest 的 token,它继承于 user’s token 与用户的登录相关联。因此,系统或域中已有的任何审计仍然可以使用。
根据设计,沙箱 token 不能保护下面这些不安全资源:
- 挂载的FAT或FAT32卷: 它们的安全描述符是 NULL。在 target 中运行的恶意软件可以读写这些磁盘空间。
- TCP/IP: Windows 200和Windows XP(但在Vista中不会)中的TCP/IP socket的安全性实际上是无效的。target 中的恶意代码可能会向任何主机发送和接收网络数据包。
- 一些未标记的 objects,例如匿名共享内存 (e.g. bug 338538)
The Job object
target 进程也运行于 object 之下,使用这个 Windows 机制,一些没有传统对象或安全描述符的的全局限制被强制执行:
- 禁止用SystemParametersInfo()做用户共享的系统范围的修改,这可以用于切换鼠标按钮或者设置屏幕保护程序超时
- 禁止创建或修改桌面对象
- 禁止修改用户共享的显示设置,比如分辨率和主显示器
- 禁止读写剪贴板
- 禁止设置全局Windows hook(使用SetWindowsHookEx())
- 禁止访问全局原子表
- 禁止访问在作业对象外创建的USER句柄
- 单活跃的进程限制(不允许创建子进程)
Chromium 渲染器通常在这些限制全部都开启后运行。每个渲染器运行在其自己的 Job object 里。
The alternate desktop
token 和 job object 规定了一个安全边界。
- 只要进程的 token 相同且处于同一 job object 下,那么他们同处于同一个安全的上下文环境中。
- 在同一桌面下具有有窗口的应用程序也处于同一个安全的上下文环境中。因为发送和接收窗口消息不受任何安全检查的约束。
在标准Windows安装中,至少有两个桌面和交互式窗口站相关联:常规(默认)桌面和登录桌面。Sandbox创建了与所有目标进程关联的第三个桌面。此桌面不可见也不能交互,并且有效地隔离沙箱化进程,使其不能窥探用户的交互,不能在更多特权的环境下发送消息到Windows。
唯一的缺点是会使用4MB 的 RAM,Vista下可能会更多。
The integrity levels
Integrity 级别是由一组特殊的 SID 和 ACL 条目实现的,它们代表五个不断增加的特权级别:untrusted, low, medium, high, system
如果一个对象处于比请求者更高级的信用等级,访问该对象就会受限。
Integrity 级别还实现了用户界面权限隔离,Integrity 性级别的规则应用于在同一桌面下的不同进程之间交换窗口消息
token 可以向更高 level 的 object 读数据,但是不能写数据。
大多数桌面应用程序以 medium integrity (MI)信任等级运行,信任度较低的程序(保护模式下的 IE 和 GPU 的 Sandbox)以 low integrity (LI) 信任等级运行,而 renderer 则以最低的信任等级运行。
一个低 level 的 token 只能访问以下资源:
- 从大部分的文件里读取数据
- 可以向
%USER PROFILE%\AppData\LocalLow
写数据 - 读注册表的大部分内容
- 可以向
HKEY_CURRENT_USER\Software\AppDataLow
写数据 - 剪贴板(为某些格式做复制粘贴)
- 远程过程调用(RPC)
- TCP/IP Socket
- 通过ChangeWindowMessageFilter暴露窗口消息
- 通过LI标签共享内存
- 拥有LI启动激活的权限,访问COM接口
- 通过LI标签暴露的命名管道
Process mitigation policies
沙箱通过 SetProcessMitigationPolicy
方法来给 target 进程设置保护措施,以强化安全特性。
1 | Data Execution Prevention: |
Sandbox policy
target 进程的实际限制由 Sandbox policy 进行配置。Sandbox policy 只是一个编程接口,borker 调用它来定义限制和允许。四个功能控制限制,大致对应四个Windows机制:
TargetPolicy::SetTokenLevel()
TargetPolicy::SetJobLevel()
TargetPolicy::SetIntegrityLevel()
TargetPolicy::SetDesktop()
前三个调用使用一个整数级别的参数,该参数从非常strict 到 非常 loose。例如,token 有 7 个 level、job 有 5 个 level,Chromium渲染器通常以四种机制中最严格的级别运行。最后一个(桌面)策略是二进制的,只能用于检测目标是否在备用桌面上运行。
Target bootstrapping
target 不伴随着限定策略一同执行,它们从一个和常规用户进程 token 非常相似的token开始执行。因为在进程引导过程中,操作系统加载程序需要访问大量资源,其中大部分是未认证且随时会变化的。另外,大部分应用程序使用标准开发工具提供的标准CRT,在进程得到引导后,CRT也需要初始化,这时CRT初始化的内部再次变成未认证状态了。
在引导阶段,进程实际上使用了两种令牌(token):
锁定令牌(lockdown token),即进程令牌
初始令牌(initial token),即设置为初始线程的模拟令牌
事实上,真正的SetTokenLevel定义是:
1 | SetTokenLevel(TokenLevel initial, TokenLevel lockdown) |
在所有的初始化操作完成后,main()或WinMain()会继续执行,还有两个令牌会存活,但只有初始线程可以使用更强大的那个初始令牌。target的责任是在准备完成后销毁初始令牌。通过调用下面函数实现:
1 | LowerToken() |
调用完LowerToken()
后,target 唯一可用的 token 便是 lockdown token,并且在此之后完整的沙箱限制开始生效。该调用不可撤销。注意,初始令牌是模拟令牌仅对主线程有效,target 进程中创建的其他线程仅使用锁定令牌,因此不会尝试获取符合安全检查的任何系统资源。
target 始于特权令牌,这简化了explicit policy,因为任何需要在进程启动时执行一次的特权操作都可以在LowerToken()
调用之前完成,并且不需要在策略中包含规则。
NOTE
确保在调用 LowerToken()
之前关闭使用初始令牌获得的任何敏感操作系统句柄。任何泄露的句柄都可能被恶意软件滥用以逃离沙箱。
参考
https://chromium.googlesource.com/chromium/src/+/HEAD/docs/design/sandbox_faq.md
https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/design/sandbox.md