第 4 章:沙箱机制详解
第 4 章:沙箱机制详解
本章目标:深入理解 Flatpak 沙箱的底层实现、权限体系和 Portal API,学会精确控制应用权限。
4.1 沙箱底层技术
4.1.1 Linux Namespace
Flatpak 沙箱建立在 Linux 内核的 Namespace 机制之上:
| Namespace | 用途 | Flatpak 中的作用 |
|---|---|---|
| PID | 进程隔离 | 沙箱内应用看不到宿主的其他进程 |
| Mount | 文件系统挂载点隔离 | 应用只能看到被授权的目录 |
| Network | 网络栈隔离 | 可选:隔离网络访问 |
| IPC | 进程间通信隔离 | 隔离 System V IPC 和 POSIX 消息队列 |
| User | 用户/组 ID 隔离 | 沙箱内以不同 UID 运行 |
4.1.2 Bubblewrap (bwrap)
Bubblewrap 是 Flatpak 的沙箱执行器,通过 Linux Namespace 和 seccomp 系统调用过滤来创建轻量级沙箱。
# 查看 flatpak 应用的 bwrap 进程
ps aux | grep bwrap
# 手动使用 bwrap 创建简单沙箱(学习用途)
bwrap \
--ro-bind / / \
--dev /dev \
--proc /proc \
--unshare-pid \
--die-with-parent \
/bin/bash
# 在沙箱中尝试操作
ls /home/ # 可以看到(因为 --ro-bind / /)
touch /test.txt # 失败!(只读绑定)
4.1.3 Seccomp 过滤
Seccomp (Secure Computing Mode) 限制应用可使用的系统调用:
# 查看 Flatpak 的 seccomp 规则
# (存储在应用的 metadata 中)
flatpak info --show-metadata org.gnome.Calculator | grep -A 20 "\[Context\]"
# Flatpak 默认禁止的危险系统调用包括:
# - clone (with certain flags)
# - ptrace
# - kexec_load
# - open_by_handle_at
# - init_module / delete_module
4.2 权限体系
4.2.1 权限声明方式
权限在 Manifest 中通过 [Context] 段声明:
# Manifest 中的权限声明示例
finish-args:
# 显示
- "--socket=wayland"
- "--socket=fallback-x11"
# 网络
- "--share=network"
# 音频
- "--socket=pulseaudio"
# GPU
- "--device=dri"
# 文件访问
- "--filesystem=~/Documents"
# D-Bus
- "--talk-name=org.freedesktop.Notifications"
# 环境变量
- "--env=APP_VARIANT=flatpak"
4.2.2 权限分类详解
网络权限
| 权限 | 效果 |
|---|---|
--share=network | 允许网络访问 |
--unshare=network | 禁止网络访问 |
# 测试网络权限
flatpak run --unshare=network org.gnome.Calculator
# 沙箱内 ping 将失败
# 查看应用的网络权限
flatpak info --show-permissions org.mozilla.firefox | grep -i network
文件系统权限
| 权限 | 效果 | 风险 |
|---|---|---|
--filesystem=home | 读写整个 $HOME | 高 |
--filesystem=host | 读写整个文件系统 | 极高 |
--filesystem=~/Documents | 读写 ~/Documents | 中 |
--filesystem=xdg-documents | 读写 XDG 文档目录 | 中 |
--filesystem=/tmp | 读写 /tmp | 中 |
--filesystem=ro:~/Pictures | 只读访问 ~/Pictures | 低 |
--filesystem=~/test:create | 读写 ~/test,不存在则创建 | 中 |
# 查看文件系统挂载
flatpak run --command=mount org.gnome.Calculator
# 沙箱内的文件系统结构
# /app/ → 应用文件(只读)
# /usr/ → 运行时文件(只读)
# /run/user/<uid>/ → 用户运行时目录
# ~/.var/app/<id>/ → 应用私有目录(读写)
# /tmp/ → 临时目录(隔离)
# 查看应用私有目录
ls ~/.var/app/org.gnome.Calculator/
# cache/ → 缓存文件
# config/ → 配置文件
# data/ → 数据文件
设备权限
| 权限 | 效果 |
|---|---|
--device=dri | GPU 加速(DRI 设备) |
--device=shm | POSIX 共享内存 |
--device=all | 所有设备(包括 USB、摄像头等) |
# 查看 GPU 设备在沙箱中的映射
flatpak run --command=ls org.gnome.Calculator /dev/dri/
D-Bus 权限
| 权限 | 效果 |
|---|---|
--talk-name=org.freedesktop.* | 允许与指定服务通信 |
--own-name=com.example.App | 允许注册 D-Bus 名称 |
--no-talk-name=* | 禁止所有 D-Bus 通信 |
--system-talk-name= | 系统总线通信权限 |
# 查看应用的 D-Bus 权限
flatpak info --show-permissions org.gnome.Calculator
4.3 Portal API
4.3.1 Portal 概念
Portal 是 Flatpak 沙箱与宿主系统之间的标准化接口。它通过 D-Bus 实现,由 xdg-desktop-portal 提供。
┌─────────────────────────────────────────────────────────┐
│ 沙箱内应用 │
│ ┌───────────────────────────────────────────────────┐ │
│ │ 应用代码 → Portal 客户端库 (libportal) │ │
│ └───────────────────────┬───────────────────────────┘ │
│ │ D-Bus (session bus) │
│ ┌───────────────────────┼───────────────────────────┐ │
│ │ Bubblewrap 沙箱边界 │ │
│ └───────────────────────┼───────────────────────────┘ │
└──────────────────────────┼──────────────────────────────┘
│
┌──────────────────────────┼──────────────────────────────┐
│ 宿主系统 │
│ ┌───────────────────────┼───────────────────────────┐ │
│ │ xdg-desktop-portal ←─┘ │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ 门户实现 (Portal Implementation) │ │ │
│ │ │ • FileChooser → 文件选择对话框 │ │ │
│ │ │ • Print → 打印对话框 │ │ │
│ │ │ • Notification → 系统通知 │ │ │
│ │ │ • Screenshot → 截图 │ │ │
│ │ │ • Settings → 系统设置 │ │ │
│ │ │ • Wallpaper → 设置壁纸 │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
4.3.2 常用 Portal
| Portal 名称 | D-Bus 接口 | 用途 |
|---|---|---|
| FileChooser | org.freedesktop.portal.FileChooser | 打开/保存文件对话框 |
org.freedesktop.portal.Print | 打印 | |
| Notification | org.freedesktop.portal.Notification | 系统通知 |
| Screenshot | org.freedesktop.portal.Screenshot | 截屏 |
| Location | org.freedesktop.portal.Location | 定位服务 |
| Settings | org.freedesktop.portal.Settings | 读取系统设置(主题、字体等) |
| Camera | org.freedesktop.portal.Camera | 摄像头访问 |
| ScreenCast | org.freedesktop.portal.ScreenCast | 屏幕录制/共享 |
| Inhibit | org.freedesktop.portal.Inhibit | 阻止系统休眠 |
4.3.3 测试 Portal
# 检查 Portal 服务状态
systemctl --user status xdg-desktop-portal.service
# 查看可用的 Portal 实现
busctl --user list | grep portal
# 使用 gdbus 测试 FileChooser Portal
gdbus call --session \
--dest org.freedesktop.portal.Desktop \
--object-path /org/freedesktop/portal/desktop \
--method org.freedesktop.portal.FileChooser.OpenFile \
"" "选择文件" "{}"
# 查看 Portal 后端实现
ls /usr/share/xdg-desktop-portal/portals/
# gnome-keyring.portal gtk.portal ...
4.3.4 在代码中使用 Portal
以 Python + libportal 为例:
#!/usr/bin/env python3
# portal-demo.py - 演示在 Flatpak 沙箱中使用 Portal
import gi
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gtk, Adw, Gio
import gi.repository.Xdp as Xdp
class PortalDemo(Adw.Application):
def __init__(self):
super().__init__(application_id='com.example.PortalDemo')
self.portal = Xdp.Portal()
def do_activate(self):
win = Gtk.ApplicationWindow(application=self)
win.set_title("Portal 演示")
win.set_default_size(400, 200)
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
box.set_margin_top(24)
box.set_margin_bottom(24)
box.set_margin_start(24)
box.set_margin_end(24)
# 发送通知按钮
btn_notify = Gtk.Button(label="发送通知")
btn_notify.connect("clicked", self.on_notify)
box.append(btn_notify)
# 打开文件按钮
btn_open = Gtk.Button(label="打开文件")
btn_open.connect("clicked", self.on_open_file)
box.append(btn_open)
win.set_child(box)
win.present()
def on_notify(self, button):
"""通过 Portal 发送系统通知"""
notification = Gio.Notification.new("Portal 测试")
notification.set_body("这条通知来自 Flatpak 沙箱!")
self.send_notification("portal-test", notification)
def on_open_file(self, button):
"""通过 Portal 打开文件选择器"""
win = self.get_active_window()
# 使用 Portal 的文件选择器
self.portal.open_file(
parent=Xdp.parent_new_gtk(win),
title="选择文件",
filters=None,
current_folder=None,
callback=self.on_file_selected
)
def on_file_selected(self, portal, result):
try:
uri = self.portal.open_file_finish(result)
print(f"选择的文件: {uri}")
except Exception as e:
print(f"操作取消: {e}")
if __name__ == "__main__":
app = PortalDemo()
app.run(None)
4.4 权限审计
4.4.1 查看应用权限
# 查看应用权限
flatpak info --show-permissions org.gimp.GIMP
# 输出示例:
# [Context]
# shared=network;ipc;
# sockets=x11;wayland;pulseaudio;
# devices=dri;
# filesystems=host;xdg-config/GIMP;
# [Session Bus Policy]
# org.gtk.vfs.*=talk
# org.freedesktop.FileManager1=talk
# 使用 flatpak-permission-tool 查看(如果安装了)
flatpak permissions
4.4.2 权限评估矩阵
评估一个应用权限是否合理:
| 评估项 | 合理 | 可疑 | 危险 |
|---|---|---|---|
--share=network | 浏览器、聊天工具 | 计算器、记事本 | — |
--socket=pulseaudio | 音乐播放器 | 文本编辑器 | — |
--filesystem=home | 文件管理器 | 计算器 | — |
--filesystem=host | 系统工具 | 任何应用 | — |
--device=all | 虚拟机管理器 | 文本编辑器 | 计算器 |
--talk-name=* (所有) | — | — | 任何应用 |
4.4.3 使用 Flatseal 管理权限
Flatseal 是一个图形化的 Flatpak 权限管理工具:
# 安装 Flatseal
flatpak install flathub com.github.tchx84.Flatseal
# 运行 Flatseal
flatpak run com.github.tchx84.Flatseal
Flatseal 提供的功能:
- 查看所有已安装应用的权限
- 图形化切换各项权限
- 查看权限覆盖历史
- 重置为默认权限
4.5 高级沙箱配置
4.5.1 自定义 seccomp 规则
# 创建自定义 seccomp 规则文件
cat > /tmp/my-seccomp.json << 'EOF'
{
"syscall": [
"ptrace",
"process_vm_readv",
"process_vm_writev"
],
"action": "SCMP_ACT_ERRNO"
}
EOF
# 使用自定义 seccomp 规则运行应用
# 注意:需要在 Manifest 中配置
4.5.2 自定义环境变量
# 运行时设置环境变量
flatpak run --env=GTK_THEME=Adwaita:dark org.gnome.Calculator
# 查看沙箱内的环境变量
flatpak run --command=env org.gnome.Calculator | sort
4.5.3 临时权限提升
# 临时授予 host 文件系统访问(不修改 Manifest)
flatpak run --filesystem=host org.gnome.TextEditor
# 临时授予设备访问
flatpak run --device=all org.gimp.GIMP
# 临时禁止网络
flatpak run --unshare=network org.gnome.Calculator
# 临时启用开发者功能
flatpak run --devel org.gnome.Builder
4.6 常见沙箱问题与解决方案
问题 1:应用无法访问特定目录
# 症状:应用无法读写 ~/Downloads 中的文件
# 原因:应用没有 filesystem 权限
# 解决:
flatpak override --user --filesystem=~/Downloads org.example.App
问题 2:应用无法使用系统主题
# 症状:应用使用默认主题,不跟随系统主题
# 原因:缺少主题扩展
# 解决:
# 1. 安装主题的 Flatpak 扩展
flatpak install flathub org.gtk.Gtk3theme.Adwaita-dark
# 2. 或使用主题扩展(参见第 8 章)
问题 3:应用无法启动(权限不足)
# 查看错误日志
flatpak run --verbose org.example.App 2>&1 | grep -i "error\|denied\|permission"
# 常见原因:
# - 缺少 socket 权限
# - 缺少 D-Bus 访问权限
# - seccomp 阻止了必要的系统调用
问题 4:应用声音异常
# 检查 PulseAudio 权限
flatpak info --show-permissions org.example.App | grep pulseaudio
# 检查 PulseAudio 服务
pactl info
# 在 PipeWire 环境下检查
pw-cli info 0
4.7 业务场景
场景 1:企业安全合规
企业要求所有桌面应用必须进行沙箱隔离,以保护敏感数据:
# 创建企业应用权限策略文件
cat > /etc/flatpak/enterprise-policy.json << 'EOF'
{
"allowed-permissions": [
"network",
"wayland",
"pulseaudio",
"dri"
],
"denied-permissions": [
"host",
"home",
"all-devices"
],
"allowed-dbus-talk": [
"org.freedesktop.Notifications",
"org.freedesktop.portal.*"
]
}
EOF
# 使用 Flatpak override 批量应用策略
for app in $(flatpak list --app --columns=application); do
flatpak override --system --reset "$app"
flatpak override --system --unshare=network "$app" # 示例:禁止网络
done
场景 2:儿童安全模式
为孩子的账户限制应用权限:
# 禁止所有应用的网络访问
for app in $(flatpak list --user --app --columns=application); do
flatpak override --user --unshare=network "$app"
done
# 只允许特定应用访问网络
flatpak override --user --share=network org.mozilla.firefox
4.8 注意事项
⚠️ 沙箱不是万能的
Flatpak 沙箱主要保护文件系统和系统资源,但应用在沙箱内仍可执行任意代码。恶意应用可能利用 Portal 获取文件内容。始终只安装受信任来源的应用。
⚠️ X11 安全限制
在 X11 下,沙箱内的应用可以截取其他窗口的内容。如需最高安全性,请使用 Wayland 会话。
⚠️ D-Bus 会话总线
默认情况下,应用可以访问 D-Bus 会话总线。某些应用可能通过 D-Bus 获取不应有的信息。建议使用--no-talk-name=*并明确列出需要的 D-Bus 名称。