强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Flatpak 应用打包完整教程 / 第 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=driGPU 加速(DRI 设备)
--device=shmPOSIX 共享内存
--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 接口用途
FileChooserorg.freedesktop.portal.FileChooser打开/保存文件对话框
Printorg.freedesktop.portal.Print打印
Notificationorg.freedesktop.portal.Notification系统通知
Screenshotorg.freedesktop.portal.Screenshot截屏
Locationorg.freedesktop.portal.Location定位服务
Settingsorg.freedesktop.portal.Settings读取系统设置(主题、字体等)
Cameraorg.freedesktop.portal.Camera摄像头访问
ScreenCastorg.freedesktop.portal.ScreenCast屏幕录制/共享
Inhibitorg.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 名称。


4.9 扩展阅读