开源项目
我为什么为 SOL 设计 SCP:把窗口协议变成安全边界
SOL Compositor Protocol 不只是另一套窗口消息格式。它把应用身份、能力令牌、可信系统界面与用户意图放进 compositor 的原生契约,尝试从协议层收紧桌面应用的权限边界。
桌面上的一个普通应用,可能接触键盘输入、剪贴板、拖放内容、屏幕画面、全局快捷键和多个窗口。我们习惯把这些看作“桌面功能”,但从安全角度看,它们都是权力:读取剪贴板可能拿到密码,截屏可能带走私密信息,伪造系统界面可能诱导用户授权,而占据特殊窗口层级甚至可以改变用户看到的整个桌面。
这也是我为 SOL 设计 SCP(SOL Compositor Protocol) 的原因。
SCP 不只是为了换一种消息格式,也不是为了证明我们能重新发明窗口协议。它要回答的是一个更基础的问题:如果 SOL 从一开始就是一套 application-first、能力导向的操作系统,那么连接应用与 compositor 的协议,为什么不直接表达应用是谁、它拥有什么权力,以及这项权力在什么条件下才有效?
窗口协议不应该只负责搬运像素和输入事件。它也应该是一份可验证的权限契约。
问题不只是“能不能显示一个窗口”
成熟的 Linux 图形栈已经非常强大。Wayland 把协议核心保持得小而清晰,compositor、portal 和扩展协议可以在其上建立策略。这对通用 Linux 生态很重要;SCP 的出发点并不是简单地说“Wayland 不安全”。
SOL 面对的是另一组产品约束。它不准备成为可以运行任意 Linux 桌面应用的传统发行版,而是希望像 Android 或 ChromeOS 那样,定义自己的应用模型、运行时、打包格式和系统边界。在这个前提下,如果我们一边设计按应用身份授权的系统,一边又让图形协议暴露一套与身份无关的全局能力,安全模型就会被拆成两半:协议先允许,外围组件再尝试补救。
SCP 选择反过来做。一个客户端在创建敏感对象前,先要回答三个问题:
- 你是谁? 连接必须绑定到操作系统验证过的应用身份,而不是相信客户端自报的名字。
- 你被允许做什么? 创建顶层窗口、进入全屏、读取剪贴板、截屏或占用系统层级,是不同作用域的能力。
- 为什么现在允许? 有些能力不仅依赖静态权限,还依赖前台焦点、最近一次真实交互或本次用户确认。
这三个问题组成了 SCP 的基本安全模型:identity first,capability next,context always。
一次 SCP 连接是怎样建立的
当前原型使用 $XDG_RUNTIME_DIR/sol-compositor-0 下的 Unix domain socket。socket 权限被收紧为 0600;消息使用 4 字节大端长度前缀,单帧限制为 1 MiB;共享缓冲区的文件描述符通过 SCM_RIGHTS 传递,而不是相信 JSON 里由客户端填写的整数。
连接流程大致如下:
App process
│ Connect { app_id, pid }
▼
SCP transport ── SO_PEERCRED 验证真实 PID
│
▼
SecurityCoordinator ── PID → AppId,评估能力
│
▼
Connected { session_id, capability tokens }
│
▼
CreateSurface → CreateToplevel(token) → Configure → Commit
传输层先通过内核提供的 SO_PEERCRED 读取对端 PID;如果消息里声称的 PID 与真实连接者不一致,连接会被拒绝。认证成功后,compositor 为连接建立 session,并返回作用域明确的 opaque token。客户端本地的 surface ID 只在自己的 session 中有效,另一个客户端不能拿同一个数字操作它的 surface。
创建普通 surface 本身不等于获得窗口权力。当客户端请求 CreateToplevel 时,还必须提交属于这个应用、这个 session 和 window-toplevel 能力的 token。伪造 token、过期 token、能力作用域不匹配,或者试图操作其他 session 的对象,都会被拒绝。
这条路径现在已经不是纯设计图。仓库里的原生示例客户端可以完成真实的 connect → surface → toplevel → configure → commit 往返;集成测试也覆盖了 PID 冒充、伪造能力令牌、跨 session 对象隔离,以及通过 SCM_RIGHTS 传递缓冲区 FD。
能力不是一张永久通行证
传统权限界面很容易把授权简化成一个布尔值:允许或拒绝。但桌面能力通常还需要范围、时间和交互上下文。
SCP 因此把能力设计成独立、可审计的授权单元。下面是协议希望建立的边界;其中一部分已经进入状态机和 token 验证,另一部分仍需要 sol-securityd、Shell 和真实输入链路完成端到端集成。
| 能力 | 目标策略 | 为什么不能只用一个总开关 |
|---|---|---|
| 顶层窗口 | 普通应用默认获得,创建时仍验证 token | 防止跨 session 伪造和对象劫持 |
| Popup | 必须绑定属于同一应用的父 surface | 菜单不能逃离父窗口的生命周期 |
| 剪贴板读取 | 只允许当前前台应用 | 后台进程不应静默收集复制内容 |
| 剪贴板写入 | 需要近期真实用户交互 | 防止后台应用污染剪贴板 |
| 拖放 | 由真实指针交互启动,并绑定 serial | 防止合成一次不存在的拖放 |
| 屏幕捕获 | 按窗口、输出或工作区授权,并要求用户确认 | “能截屏”不应等于“永远能截一切” |
| 全局快捷键 | 声明用途并单独授权 | 防止应用抢占系统或其他应用的按键 |
| Layer shell | 仅保留给认证过的 sol-shell |
第三方应用不能伪造状态栏或系统面板 |
| 全屏 | 独立于普通窗口能力 | 全屏会遮挡系统界面,应有更强约束 |
token 也不是为了给旧式 ACL 换个名字。它可以带有过期时间,也可以设计成一次性使用;compositor 在执行具体操作时再次校验 token 的应用身份和能力作用域,而不是只在连接时检查一次。未来当用户撤销授权时,sol-securityd 可以使 token 失效,compositor 则停止对应能力,而不必终止整个应用。
最终目标是让每次敏感操作都留下可以解释的记录:哪个 AppId,在什么上下文里,请求了什么能力,结果是授权、拒绝还是实际使用。可审计性必须和授权走同一条路径,而不是依赖应用自觉写日志。
可信界面必须由系统拥有
权限模型还有一个经常被忽视的部分:用户如何知道自己看到的是系统,而不是应用画出来的一块相似界面?
SCP 的答案是强制 server-side decoration。标题栏、关闭按钮、应用身份标识和权限对话框属于 compositor 与 Shell 的可信界面,普通客户端不能替换它们。客户端可以提供窗口标题,但窗口位置、系统 chrome 和特殊层级由系统控制。
这不是单纯的视觉一致性选择。只要应用能够无差别地绘制一套看起来像系统的标题栏、状态栏或授权提示,用户就很难判断自己正在信任谁。SOL 想让“这是系统界面”成为架构保证,而不是一条设计规范。
当前 SCP 已经在 ConfigureToplevel 中预留 compositor decoration 的尺寸,状态机也由 compositor 分配窗口大小和状态;真正的标题栏绘制、点击关闭和完整的反钓鱼体验仍要接入原生渲染器。这里同样需要区分协议契约和已经呈现在屏幕上的产品能力。
为什么愿意放弃现成兼容性
设计 SCP 最昂贵的决定不是写协议,而是接受它对生态的影响。
如果 SOL 最终以 SCP 作为唯一的原生 compositor 协议,它就不能承诺任意 Wayland 应用无需修改即可运行。标准调试工具也不能直接使用;SolKit、sol-app SDK、示例应用、inspector、trace、fuzzer 和迁移文档都需要自己建设。协议、renderer、Shell、输入法和辅助技术之间的验证成本,不会因为架构更干净而消失。
但维持两套产品级协议同样不是免费的。每个窗口生命周期要实现两次,每条敏感能力要在旧协议上重新过滤,每项新保证都可能被兼容路径绕开。对一个资源有限、仍在 pre-alpha 阶段的项目来说,“全部兼容”和“边界可证明”很难同时成为第一优先级。
所以 SOL 选择把方向说清楚:它是一套 Linux-family OS,而不是另一款通用 Linux 发行版。Linux 内核、驱动和成熟系统组件仍然是基础,但应用面向 SOL Runtime 与 SolKit,图形客户端最终面向 SCP。仓库中的 Smithay/Wayland frontend 目前仍作为过渡开发路径存在,原生 SCP 渲染接入后才会逐步退出;这不是已经完成的迁移。
SCP 现在真正完成了什么
截至 2026 年 8 月 26 日,SCP 仍处于早期实现阶段。当前可以由代码和测试支持的事实包括:
- 独立的 Unix socket 传输、长度 framing、消息上限、peer credential 验证和安全的 stale-socket 处理;
- 后端无关的 SCP listener,可在 winit、udev 和 headless 模式启动;
- 已认证 session、应用与 session 绑定的 capability token,以及 surface/toplevel 所有权校验;
- surface 的创建、销毁、attach、damage、pending state 与原子 commit 的状态机;
- toplevel、popup、输入事件和输出管理的协议类型与基础状态管理;
- 原生示例客户端,以及覆盖真实 socket 往返、PID 冒充、token 伪造和 FD 传递的测试。
还不能对外宣称完成的部分同样重要:
- 原生 renderer 还没有真正消费 SCP surface 并把它们合成到屏幕;
Damage、input region、buffer release、DMA-BUF 和真实输入/输出链路仍需集成;- 生产级 AppId 认证、签名 token、用户同意、动态撤销和审计仍由 stub
SecurityCoordinator占位; - server-side decoration、屏幕捕获、剪贴板、拖放与全局快捷键还没有完成端到端安全闭环;
- 当前 wire payload 仍是便于 Phase 1 开发的 Rust/Serde JSON,稳定 schema 与版本演进机制尚未定型;
scp-inspector、trace/replay、fuzzer、第三方 SDK 和迁移指南仍在路线图上。
换句话说,SCP 已经证明了传输、身份绑定、对象隔离和 capability-gated 请求可以在真实进程间跑通;它还没有证明自己是一套可供日常桌面使用的完整图形协议。下一阶段最关键的里程碑,不是再增加更多消息枚举,而是让原生 renderer 和输入循环消费这些状态,并在真实硬件上验证整个闭环。
我希望 SCP 最终证明什么
SCP 的价值不会由代码行数决定,也不会由“自研协议”四个字决定。它最终需要证明四件事:安全边界比兼容层更容易解释和测试;应用开发者不必直接承受底层协议复杂度;普通用户能感受到一致而可信的系统行为;项目愿意为自己的取舍承担生态成本。
如果这些目标做不到,SCP 就只是一套昂贵的新格式。如果能够做到,它会成为 SOL 最重要的系统契约之一:应用可以自由绘制内容,但不能自由伪造身份;可以请求能力,但不能把请求当成所有权;可以获得数据,但每次访问都带着清楚的范围和用户意图。
你可以从 SCP 协议概览、ADR-0027、实现清单 和 原生示例客户端 开始阅读,也可以直接查看 SOL 仓库。
这篇文章与 可验证的 A/B 启动与恢复、SOL 的最小授权系统 和 应用签名与 publisher lineage 共同组成 SOL 系统契约系列。
我尤其希望听到来自协议设计、Linux graphics、Rust、capability security、输入法、辅助技术和应用框架开发者的反对意见。SCP 现在还足够早,最有价值的贡献不只是帮它实现更多功能,也可能是指出哪一条安全假设根本不成立。
评论
正在加载评论。