首页
/ Ladybird 浏览器多进程架构解析:进程隔离、沙箱机制与跨进程调用链

Ladybird 浏览器多进程架构解析:进程隔离、沙箱机制与跨进程调用链

2026-09-04 15:38:30作者:凤尚柏Louis

本文基于 Ladybird 官方文档 Documentation/ProcessArchitecture.md,系统讲解该浏览器的多进程架构设计:每个标签页独占一个 WebContent 渲染进程,网络请求与图片解码分别剥离到 RequestServer 与 ImageDecoder 独立进程中,并深入源码层剖析进程拉起方式、沙箱实现(pledge()/unveil())以及从 OutOfProcessWebViewWeb::Document 的跨进程对象模型。读完本文,你可以理解 Ladybird 如何以进程边界换取面对恶意网页内容时的稳定性与安全性,并能定位到仓库中对应的服务入口、IPC 协议与沙箱代码。

一、整体进程模型

Ladybird 采用多进程架构(multi-process architecture)来提升面对任意(甚至可能是恶意的)网页内容时的稳定性与安全性。其核心分工可以概括为:

  • Browser(浏览器主应用):每个实例可以打开一个或多个标签页(tab);
  • WebContent:每个标签页对应一个为其专属拉起的 WebContent 服务进程,负责渲染该页内容;
  • RequestServer:网络请求(HTTP/HTTPS 等)从 WebContent 中剥离出来,交给独立的 RequestServer 进程;
  • ImageDecoder:图片解码(PNG、JPEG、BMP、ICO、PBM 等)也从 WebContent 剥离,交给独立的 ImageDecoder 进程。

Ladybird 进程架构总览:Browser 主进程为每个标签页拉起 WebContent,网络与图片解码分别隔离到 RequestServer 与 ImageDecoder

这种“渲染、网络、解码三分离”的结构意味着:一张格式恶意的图片崩溃只会牺牲一个短命的 ImageDecoder 进程;一个页面里的网络逻辑异常也影响不到 UI 主进程。所有进程都会使用 pledge()unveil() 机制进行激进沙箱化,并且除 Browser 外,其余进程都以与桌面主用户隔离的无权限用户身份运行。

需要说明的是,官方文档开头明确标注:

NOTE: This document is partly aspirational, in that the state of the code does not yet fully reflect what's described here. Implementation is underway.

即文档描述的某些设计(例如系统级 LookupServer 处理全部出站 DNS)是目标形态,当前代码尚在演进中。本文在介绍文档蓝图的同时,会结合当前仓库源码指出实际实现状态。

二、三大进程的职责边界

2.1 WebContent 进程:HTML/CSS 引擎与 JS 运行时

WebContent 进程承载整个浏览器的“大脑”:

  • 托管主 HTML/CSS 引擎 LibWeb
  • 运行 JavaScript 引擎 LibJS
  • 接收来自 Browser 进程输入事件,并将网页内容绘制到共享位图(shared bitmaps)中;
  • 与外界通信只能通过 RequestServer 进行——从源码结构看,WebContent 自身不直接持有网络出口。

WebContent 服务入口 中可以验证这一设计。该进程启动时的关键步骤包括:

  1. 初始化事件循环并安装 LibWeb 的事件循环插件(Web::Platform::EventLoopPlugin::install);
  2. 通过 Web::Bindings::initialize_main_thread_vm(Web::HTML::AgentType::SimilarOriginWindow) 初始化 JavaScript 主线程虚拟机——这正是 LibJS 在 WebContent 进程内运行的落点;
  3. 在沙箱启用时调用 RendererSandbox::apply_sandbox(config_path, cache_path)(见 main.cpp 第 239-240 行),把文件系统等权限收窄到配置目录与缓存目录;
  4. 注册两个跨进程回调,分别是连接资源加载器(即 RequestServer)与连接图片解码器。

其中第 4 步是理解“WebContent 只能经 RequestServer 与外界通信”的关键。在 Services/WebContent/main.cpp 第 252-259 行

webcontent_client->on_request_server_connection = &heap {
    if (auto result = connect_to_resource_loader(heap, handle); result.is_error())
        dbgln("Failed to connect to resource loader: {}", result.error());
};
webcontent_client->on_image_decoder_connection = [](auto const& handle) {
    if (auto result = connect_to_image_decoder(handle); result.is_error())
        dbgln("Failed to connect to image decoder: {}", result.error());
};

connect_to_resource_loader() 收到连接句柄后,用其创建 IPC::Transport,构造一个 Requests::RequestClient,然后交给 Web::ResourceLoadermain.cpp 第 272-285 行);connect_to_image_decoder() 则构造 ImageDecoderClient::Client 并安装为 Web::Platform::ImageCodecPlugin 的实现(main.cpp 第 287-300 行)。也就是说,LibWeb 内部发起的一切资源请求与图片解码,最终都被转发到另外两个进程去执行——文档中“剥离网络与解码”的设计在这里得到了直接的代码印证。

WebContent 进程还暴露了相当完整的命令行开关,均通过 Core::ArgsParser 解析(main.cpp 第 155-190 行),其中与安全/隔离直接相关的包括:

参数 作用
--site-isolation <mode> 站点隔离模式,可取 disabletop-level(默认)或 iframe,解析逻辑位于 LibWebView 的 SiteIsolation 模块
--certificate <path> 指定信任的证书文件('C' 短选项)
--mach-server-name <name> 指定 Mach 服务名(macOS 下用于查找浏览器进程)
--wait-for-debugger 启动后等待调试器附加
--disable-sandbox 禁用进程沙箱(默认启用沙箱)
--enable-http-memory-cache 开启 HTTP 内存缓存
--headless 以无头模式运行
--test-mode / --expose-internals-object / --expose-experimental-interfaces 测试与实验性接口开关

2.2 RequestServer 进程:唯一的网络出口

文档描述:RequestServer 进程可以使用网络协议(如 HTTP 或 HTTPS)向外请求文件;每个 WebContent 进程都拥有自己专属的一个 RequestServer,代表它进行上传下载。对于 DNS 解析,文档蓝图是 RequestServer 向系统级的全局 LookupServer 服务求助,由后者统一处理所有出站 DNS 请求。

从当前仓库源码看,RequestServer 已具备完整的独立服务能力,其入口 Services/RequestServer/main.cpp 的主要流程:

  1. 解析命令行参数,支持:
    • --certificate <path>'C'):信任的证书文件,当前仅支持单个根证书(源码中留有 FIXME:尚不支持多张自定义根证书);
    • --http-disk-cache-mode <mode>:HTTP 磁盘缓存模式,可取 enabled(Normal)、testing(Testing)或 disabled,由 LibHTTP 的 DiskCache 模块 实现;
    • --resource-map <path>:加载 URL 到本地文件的 JSON 映射,由 RequestServer::ResourceSubstitutionMap::load_from_file 载入,便于测试时把远端资源替换为本地文件;
    • --cache-path <path>:profile 缓存目录;
    • --wait-for-debugger / --disable-sandbox / --mach-server-name
  2. 初始化 libcurl(RequestServer::initialize_libcurl()),说明其网络实现基于 curl 栈(对应 Services/RequestServer/CURL.h 等文件);
  3. 若未禁用沙箱,调用 RequestServer::apply_sandbox(certificates, cache_path)main.cpp 第 106-107 行),沙箱实现位于 Services/RequestServer/Sandbox.h 及其 Linux/macOS 平台变体;
  4. 通过 IPC::take_over_accepted_client_from_system_server<RequestServer::ConnectionFromClient>(...) 接管系统服务器转交的连接,进入事件循环。

值得注意的是 DNS 部分:当前仓库中 RequestServer 自带 Resolver 模块,负责解析;文档中“系统全局 LookupServer 统一处理出站 DNS”的描述属于 SerenityOS 式系统设计的延续,属于文档标注的“aspirational”部分,当前实现可以推断为由 RequestServer 自行完成解析,这一点在仓库中没有全局 LookupServer 服务目录作为佐证。

2.3 ImageDecoder 进程:每张图片一个全新进程

ImageDecoder 进程负责把 PNG、JPEG、BMP、ICO、PBM 等编码格式解码为位图。它的隔离强度是三者中最高的:

  • 每张图像都在一个全新的 ImageDecoder 进程中解码
  • 这些进程被强沙箱化,除了接收编码后的位图数据并在解码成功时把位图返回给 WebContent 外,几乎不能做任何其他事。

配合 WebContent 侧的 on_image_decoder_connection 回调(见 2.1 节)可以看出调用链:LibWeb 需要解码一张图 → 经由 ImageCodecPlugin(实现为 WebView::ImageCodecPlugin)→ 通过 ImageDecoderClient::Client 的 IPC 连接 → 到达 Services/ImageDecoder 中的解码服务。解码完成(或失败崩溃)后该短命进程即被回收,恶意图片攻击的爆炸半径被压缩到单个进程内。

三、进程是如何被拉起的:portal 套接字与 SystemServer

文档对进程拉起方式给出了明确描述:

要获取一个全新的 WebContent 进程,任何具备合适文件系统权限者都可以通过连接 /tmp/session/%sid/portal/webcontent 套接字来拉起一个(%sid 为当前登录会话 id)。该套接字由 SystemServer 管理,并且会为每一个连接拉起一个 WebContent 新实例。RequestServerImageDecoder 遵循同样的基本模式,区别在于这些服务是由 WebContent 按需拉起的,而不是由 Browser 拉起。

对应到当前代码,这个“系统服务器转交连接”的机制由 LibIPC 统一实现。三个服务进程的入口都调用了同一组设施:

即客户端(Browser 或 WebContent)连接到系统服务器暴露的 portal 套接字,系统服务器接受连接后把 IPC 通道“移交”给新拉起的进程。这一模式同时解释了“每个标签页一个 WebContent”与“每个 WebContent 一个 RequestServer”的进程谱系。

在客户端一侧,浏览器应用内的 OutOfProcessWebView(前向声明见 Libraries/LibWebView/Forward.h 第 34 行)负责编排这些辅助进程的创建。Libraries/LibWebView/ProcessManager.cpp 中的 ProcessType 枚举把 WebContentRequestServerImageDecoder 等服务类型统一管理,Application.cpp 则按 ProcessType::WebContent 等类型分发启动参数与调试选项——从源码结构看,Application 层是“一个标签页 → 一条进程树”这一关系的调度中枢。

平台差异也值得注意:非 macOS 平台走上述系统服务器套接字路径;macOS 上 WebContent 通过 Mach 端口从 bootstrap server 查找浏览器端口再建立传输(Services/WebContent/main.cpp 第 242-246 行),而 Windows 上 IPC 通道还带有 InitTransport 消息用于交换对端 PID(main.cpp 第 276-279 行)。

四、沙箱:pledge/unveil 与无权限用户

文档给出的安全基线有两条:

  1. 所有进程都使用 pledge()unveil() 机制被激进地沙箱化——这两个原语分别承诺(pledge)收窄进程可用的系统调用集合、揭示(unveil)进程可访问的路径白名单;
  2. 除 Browser 外,所有进程都运行在与桌面主用户隔离的无权限用户身份下,进一步抬高跨进程提权/滥用的门槛。

当前仓库把这一机制模块化为独立组件:

  • Services/RendererSandbox.h 声明 RendererSandbox::apply_sandbox(Optional<StringView> config_path, Optional<StringView> cache_path),并配有 RendererSandboxLinux.cppRendererSandboxMacOS.cppRendererSandboxUnimplemented.cpp 三套平台实现,WebContent 进程在启动早期(见 2.1 节)即调用它,把文件系统可见性限制到配置与缓存路径;
  • RequestServer 则有自己的沙箱模块 Services/RequestServer/Sandbox.h(含 SandboxLinux.cpp / SandboxMacOS.cpp / SandboxUnimplemented.cpp),在 initialize_libcurl() 之后、接管 IPC 连接之前调用,确保 curl 网络栈运行在收窄后的权限域内;
  • 两条路径都提供 --disable-sandbox 开关以便调试,但默认沙箱是开启的。

由于各服务以不同平台策略执行沙箱(Unimplemented 变体用于尚无原生沙箱支持的系统),阅读源码时应注意“平台能力决定实际隔离强度”这一前提。

五、类级结构:从 OutOfProcessWebView 到 Web::Document

文档的“Class overview”一节给出了进程内部的对象层级,配图如下:

Ladybird 跨进程类结构图:OutOfProcessWebView 经 WebContentClient 与 WebContent 进程内的 ConnectionFromClient/PageHost 相连,Page 持有 Frame 与 Document

按文档描述,各层职责如下:

  1. OutOfProcessWebView(GUI 应用进程内):一个被放置在窗口某处的控件,负责拉起所有辅助进程等杂务;
  2. WebContentClientOutOfProcessWebView 内部持有,实现 WebContent IPC 协议的客户端侧
  3. WebContent::ConnectionFromClient(WebContent 进程内):WebContentClient 的对端,其内部持有 WebContent::PageHost,而 PageHost 托管着 LibWeb 引擎的主对象 Web::Page
  4. Web::PageWeb::Frame:一个 Page 拥有一个主 Frame,并可能有与 <frame>/<iframe> 对应的子 Frame;
  5. Web::Document:每个 Frame 持有一个 Document,即 DOM 树的根节点。

这些关键类在当前仓库中均可直接定位:

六、与文档蓝图的差异及源码佐证小结

文档蓝图 当前仓库状态 依据
每标签页一个 WebContent,网络/解码外置 已实现,且 WebContent 通过 on_request_server_connection / on_image_decoder_connection 回调接入外部服务 Services/WebContent/main.cpp 第 252-259 行
portal 套接字 + SystemServer 按连接拉起进程 以 LibIPC 的 take_over_accepted_client_from_system_server 实现 Services/RequestServer/main.cpp 第 114-121 行
所有进程 pledge/unveil 沙箱 + 无权限用户 模块化为 RendererSandbox / RequestServer Sandbox,按平台实现,默认开启 Services/RendererSandbox.hServices/RequestServer/Sandbox.h
系统全局 LookupServer 统一处理 DNS 当前 RequestServer 内置 Resolver,全局 LookupServer 未见对应服务目录,属于 aspirational 部分 Services/RequestServer/Resolver.h
OutOfProcessWebViewWebContentClientPageHostPage/Frame/Document 对象链 各类均存在,IPC 协议以 .ipc 文件显式定义 Services/WebContent/ConnectionFromClient.hServices/WebContent/PageHost.h

七、延伸阅读

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
980
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384