一、为什么我们需要第四个沙盒?

Linux 已有的安全机制各有短板:

  • chroot:本质不是沙盒,root 进程可轻易逃逸
  • Namespaces:仅提供隔离视图,不提供权限边界
  • cgroup:资源限制为主,不提供安全策略
  • seccomp:限制系统调用,但无法控制"在哪些文件上调用"
  • SELinux/AppArmor:功能强大,但需要 root 权限配置策略文件

Landlock 的独特定位:它是唯一一种无需 root 权限、进程可自行施加的 Linux 安全模块。一个非特权进程可以对自己的子进程(或自己)创建沙盒规则,限制其对文件系统、网络、IPC 等资源的访问。

这一特性让它在容器安全领域极具价值:每个用户可以在自己的 namespace 内定义沙盒策略,无需管理员介入。

二、Landlock 的架构哲学:Unprivileged Sandboxing

Landlock 的核心设计原则:

  1. 自助式安全:进程自己约束自己,不依赖外部管理员
  2. 层层叠加:安全规则可以嵌套组合,每一层只能收紧不能放宽
  3. 默认拒绝:未明确允许的操作一律拒绝
  4. 向后兼容:旧内核忽略 Landlock 调用,程序正常行为不受影响

2.1 Access Domain:精细化的权限分类

Landlock 将"可以进行的操作"抽象为 Access Domain,不同 ABI 版本支持的 domain 不同:

ABI 版本内核版本引入的 Access Domain
v15.13LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR | LANDLOCK_ACCESS_FS_REMOVE_DIR | LANDLOCK_ACCESS_FS_REMOVE_FILE | LANDLOCK_ACCESS_FS_MAKE_CHAR | LANDLOCK_ACCESS_FS_MAKE_DIR | LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_MAKE_SOCK | LANDLOCK_ACCESS_FS_MAKE_FIFO | LANDLOCK_ACCESS_FS_MAKE_BLOCK | LANDLOCK_ACCESS_FS_MAKE_SYM | LANDLOCK_ACCESS_FS_REFER
v25.19+LANDLOCK_ACCESS_FS_TRUNCATE(文件截断控制)
v36.2+LANDLOCK_ACCESS_FS_IOCTL_DEV(设备 ioctl 控制)
v46.7+网络访问控制(TCP bind/connect)
v56.11+LANDLOCK_ACCESS_FS_IOCTL_DEV 扩展、更细粒度设备控制

v1 已经覆盖了绝大多数文件系统操作的安全控制需求,后续版本逐步补全了截断、设备 ioctl 和网络操作。

2.2 Ruleset / Rule 层次化模型

Landlock 的策略模型非常简洁,只有三个核心对象:

enum landlock_rule_type {
    LANDLOCK_RULE_PATH_BENEATH,   // 路径规则:指定目录下的操作
    LANDLOCK_RULE_NET_PORT,       // 网络规则:指定端口(v4+)
};

struct landlock_ruleset_attr {
    __u64 handled_access_fs;    // 这个 ruleset 要管控的访问类型
    __u64 handled_access_net;   // 网络访问类型(可选)
};

struct landlock_path_beneath_attr {
    __u64 allowed_access;       // 允许的访问类型
    __s32 parent_fd;            // 父目录的 fd
};
  • Ruleset:定义"我要管控哪些类型的操作"(即 Access Domain 的总集)
  • Rule (Path/Nets):定义"在哪个对象上,允许哪些具体的操作"
  • 层次化 Nested Rulesets:子进程可以创建自己的 ruleset 叠加在父 ruleset 之上,但只能缩减权限集合

三、内核实现:LSM Hooks 上的精准拦截

Landlock 通过 Linux Security Module (LSM) 框架实现,在关键操作路径上挂载 hook 函数:

3.1 文件系统相关 Hooks

LSM Hook 点拦截的操作
file_permission文件读写 open/read/write
path_link创建硬链接
path_rename文件重命名(源 + 目标双重检查)
path_mknod创建设备文件/FIFO/socket
path_symlink创建符号链接
path_unlink删除文件
path_rmdir删除目录
inode_notifysecctx截断操作(v2+)
file_ioctl设备 ioctl(v3+)

3.2 权限检查的执行流程

用户进程做文件操作
  ↓
系统调用进入内核
  ↓
VFS 层 → 触发 LSM hook (如 file_permission)
  ↓
landlock_file_permission() 被调用
  ├─ 遍历该进程的所有 Landlock ruleset(从最新到最旧)
  ├─ 对每个 ruleset,检查路径是否匹配某条规则
  │   ├─ 匹配 && 权限允许 → 继续检查下一条
  │   ├─ 匹配 && 权限拒绝 → 返回 -EACCES
  │   └─ 不匹配 → 跳过此条规则
  ├─ 如果所有规则都检查完毕仍未允许
  └─ 如果没有任何规则覆盖该对象 → -EACCES(默认拒绝)

层次化规则的关键实现:每个进程的 struct cred 中security 指针指向 struct landlock_cred_sec,内部维护一个 struct ruleset 链表(最新的在最前面),每次检查从最新开始遍历。这样就实现了"只能收紧不能放宽"的单调递减安全保证。

四、从零写一个 Landlock 沙盒程序

下面通过一个完整示例展示如何用 C 语言使用 Landlock。

4.1 最简单的 Landlock 沙盒(ABI v1)

#include <sys/syscall.h>
#include <sys/prctl>
#include <linux/landlock.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>

int main(int argc, char *argv[])
{
    struct landlock_ruleset_attr ruleset_attr = {
        .handled_access_fs = LANDLOCK_ACCESS_FS_EXECUTE
                            | LANDLOCK_ACCESS_FS_READ_FILE
                            | LANDLOCK_ACCESS_FS_READ_DIR,
    };
    int ruleset_fd, allowed_fd;

    // 第1步:要求内核支持 Landlock(防止旧内核静默忽略)
    prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY);  // 可选: 允许 ptrace

    // 第2步:创建 ruleset
    ruleset_fd = syscall(__NR_landlock_create_ruleset,
                          &ruleset_attr, sizeof(ruleset_attr), 0);
    if (ruleset_fd < 0) {
        perror("landlock_create_ruleset");
        return 1;
    }

    // 第3步:添加规则 — 允许读取 /tmp/allowed_dir
    allowed_fd = open("/tmp/allowed_dir", O_PATH | O_CLOEXEC);
    struct landlock_path_beneath_attr path_beneath = {
        .allowed_access = LANDLOCK_ACCESS_FS_READ_FILE
                        | LANDLOCK_ACCESS_FS_READ_DIR,
        .parent_fd = allowed_fd,
    };
    syscall(__NR_landlock_add_rule, ruleset_fd,
            LANDLOCK_RULE_PATH_BENEATH, &path_beneath, 0);
    close(allowed_fd);

    // 第4步:启用 Landlock(从此刻起规则生效且不可撤回)
    if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
        perror("prctl(PR_SET_NO_NEW_PRIVS)");
        return 1;
    }
    if (syscall(__NR_landlock_restrict_self, ruleset_fd, 0)) {
        perror("landlock_restrict_self");
        return 1;
    }
    close(ruleset_fd);

    // 从此时起,进程只能读取 /tmp/allowed_dir
    // 任何其他文件访问都将得到 EACCES
    return 0;
}

4.2 Rust 使用 Landlock(使用 landlock crate)

use landlock::{
    Access, AccessFs, Ruleset, RulesetAttr, RulesetCreated, RulesetCreatedAttr,
    RulesetStatus, path_beneath_rules, AccessFs, ABI,
};
use std::os::unix::io::AsRawFd;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 定义要管控的权限集
    let abi = ABI::V1;
    let access = AccessFs::from_all(abi);

    // 创建 ruleset
    let ruleset = Ruleset::new()
        .handle_access(access)?
        .create()?;

    // 添加允许的规则
    let rules = path_beneath_rules(
        ["/var/app/data", "/tmp/work"].iter(),
        AccessFs::from_read(abi),
    );

    // 应用规则
    let _ = ruleset.add_rules(rules)?.restrict_self()?;

    // 沙盒已生效
    Ok(())
}

五、与 seccomp 和 binfmt_misc 的联合防御体系

单独使用 Landlock 已能提供有效防护,但生产环境通常需要多层纵深防御。Landlock 与 Linux 其他安全机制配合使用时形成互补:

5.1 Landlock + seccomp:最强组合

seccomp 限制"能执行哪些系统调用",Landlock 限制"能在哪些文件上执行操作"。两者结合可以实现:

/* 推荐的应用顺序 */

// 1. 先 fork,让子进程成为后续规则的目标
fork();

// 2. 启用 NO_NEW_PRIVS(提权防护)
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);

// 3. 先装 seccomp(限制 executes 以及非必要的系统调用)
seccomp(STRICT);  // 仅允许 read/write/exit/sigreturn

// 4. 再装 Landlock(限制文件访问范围)
landlock_restrict_self();

// 5. 最后 exec 目标程序
execve(target_binary);

顺序很关键:seccomp 会过滤 landlock 系统调用,所以 seccomp 必须在 Landlock 之前安装。同时,PR_SET_NO_NEW_PRIVS 必须最先设置,确保后续程序的 setuid/setgid 被禁用。

5.2 Landlock + binfmt_misc:控制二进制解释

binfmt_misc 允许内核将特定格式的文件(如 .py/.jar、Wine 程序)转交给解释器执行。在沙盒中,恶意程序可能利用 binfmt_misc 执行未授权的二进制格式。Landlock v1 的 LANDLOCK_ACCESS_FS_EXECUTE 权限可以直接限制只有特定目录的文件才能被 exec,从而阻断非授权 binfmt_misc 调用。

六、生产环境集成:容器运行时中的应用

Landlock 最初的设计动机之一就是让非特权容器内的应用能够建立自己的安全边界,不需要管理员配置 SELinux/AppArmor 策略。

6.1 Docker 集成方案

Docker 默认不使用 Landlock,但有几种方式可以启用:

方案一:使用 docker-landlock 插件/脚本

# 在 Docker 容器入口脚本中使用 Landlock
# docker-entrypoint.sh
set -e

# 编译并运行一个使用 Landlock 的最小包装器
gcc -o /usr/local/bin/landlock-wrap scripts/landlock-wrap.c || true

# 用 Landlock 限制应用只能访问必要目录,然后 exec 应用
exec /usr/local/bin/landlock-wrap \
    --allow-read /var/app/data \
    --allow-write /var/app/tmp \
    --allow-read /usr/share \
    -- \
    /usr/local/bin/my-app "$@"

方案二:通过 OCI Hook 注入 Landlock

OCI (Open Container Initiative) 允许在容器生命周期的各个阶段执行 hook 脚本。可以在 prestart hook 中加载 Landlock 策略:

// hooks.json(OCI hooks 配置)
{
  "version": "1.0.0",
  "hook": {
    "path": "/usr/local/bin/landlock-hook",
    "args": ["landlock-hook", "--config", "/etc/landlock.yaml"]
  },
  "stages": ["prestart"],
  "when": {
    "always": true
  }
}

6.2 containerd / CRI-O 集成

containerd 的自定义运行时可以集成 Landlock。一个典型实现是:

  1. 在容器配置的 OCI spec 中添加 prestart hook
  2. Hook 程序读取 YAML/JSON 策略文件
  3. 使用 landlock_create_ruleset / landlock_add_rule 系统调用配置策略
  4. 调用 landlock_restrict_self 的策略生效
  5. Hook 退出,容器主进程在沙盒内启动

6.3 LXC 集成

LXC 自 5.0 版本(2022 年开始)对 Landlock 有较好的支持。可通过配置文件配置:

# LXC 配置文件配置 Landlock
lxc.apparmor.profile = unconfined      # 不需要 AppArmor
lxc.hook.pre-start = /usr/local/bin/lxc-landlock-hook

# 策略文件: /var/lib/lxc/mycontainer/landlock.yaml
landlock:
  fs:
    deny:
      - /
    allow:
      - path: /var/www/html
        access:
          - execute
          - read_file
          - read_dir
      - path: /var/log/myapp
        access:
          - write_file
          - read_file

七、实际部署案例分析

7.1 Flatpak:Landlock 的旗舰用户

Flatpak 从 1.12 版本(2022 年中)开始默认启用 Landlock 来进一步加强沙盒安全性。Flatpak 使用 Landlock 的方式:

  • 限制范围:在已有的文件系统命名空间限制之上,再添加一层 Landlock 规则
  • 控制目标:阻止应用访问 ~/.ssh、~/.gnupg、~/.kube 等敏感路径
  • 不可撤回:应用一旦进入沙盒,无法自行解除或放宽限制
  • 双向受益:即使是 root 用户运行的应用,也受到 Landlock 限制

Flatpak 的实现命令大致为:

// Flatpak bubblewrap + Landlock 伪代码
// 1. 启动 bwrap 创建隔离环境
bwrap --ro-bind /usr /usr \
      --ro-bind /bin /bin \
      --dir /tmp \
      --proc /proc \
      --dev /dev \
      // 2. 在新的 mount namespace 中应用 Landlock
      landlock-writer \
        --fs-deny / \
        --fs-allow-read /usr \
        --fs-allow-read /tmp \
        --fs-allow-write /tmp \
        --exec firefox

7.2 Mozilla Firefox 的 Landlock 实验

Mozilla 在 Firefox 中实验性地使用 Landlock 来沙盒化媒体解析器和其他不可信内容处理进程。核心思路:将每个 Tab 的媒体解码放在独立的子进程中,用 Landlock 将其文件系统可见范围限制在缓存区域内,防止恶意媒体文件逃逸到其他文件。

7.3 systemd 服务的 Landlock 沙盒化

systemd 253+ 开始支持为服务配置 Landlock 策略:

# /etc/systemd/system/my-webapp.service
[Unit]
Description=My Web Application

[Service]
Type=simple
ExecStart=/usr/local/bin/webapp

# Landlock 规则(systemd 253+)
LandlockPaths=/var/lib/webapp:ro
LandlockPaths=/var/log/webapp:rw
LandlockPaths=/etc/ssl:ro
LandlockWritablePaths=/var/lib/webapp/tmp

# 配合其他沙盒选项
NoNewPrivileges=yes
SystemCallFilter=@system-service @module

[Install]
WantedBy=multi-user.target

八、Landlock 的限制与注意事项

8.1 已知的限制

限制说明规避方法
无网络控制(v3 之前)早期 ABI 不支持网络操作限制结合 seccomp socket 过滤 / ABI v4+
符号链接逃逸Landlock 仅检查路径,不跟踪 symlink确保沙盒路径中没有危险的 symlink,或使用 O_NOFOLLOW
不控制 inode 元数据不限制 chmod/chown 等属性操作需要结合其他安全模块
敏感信息泄露受限进程仍然可以看到 /proc 中的元数据mount namespace + hidepid 挂载选项
高权限进程绕过父进程不受子进程的 Landlock 约束对需要控制的所有进程逐一安装规则

8.2 ABI 版本检测最佳实践

在部署 Landlock 时,应当处理 ABI 版本降级侦查:

/* 推荐做法:按 ABI 版本降级尝试 */
int try_landlock(void) {
    int abi = landlock_create_ruleset(NULL, 0,
                                      LANDLOCK_CREATE_RULESET_VERSION);
    switch (abi) {
        case 5: /* v5: 网络 + Ioctl 全功能 */
        case 4: /* v4: 网络 */
        case 3: /* v3: Ioctl 控制 */
        case 2: /* v2: Truncate */
        case 1: /* v1: Basic FS 控制 */
            return setup_rules(abi);
        case -1:
            /* 旧内核不支持 Landlock,程序降级运行 */
            return 0;
    }
}

九、Landlock 的未来路线图

Landlock 仍在快速演进中,未来的关键方向:

  • Scoped Landlock:支持对 mount namespace 的 Landlock 策略继承规则
  • 网络策略增强:v4+ 已支持 bind/connect,未来将覆盖更多 socket 操作
  • IPC 管控:对 Unix domain socket、消息队列、信号量等 IPC 资源进行访问控制
  • 内核级策略持久化:允许将 Landlock 规则与 inode 绑定,自动应用于所有打开该文件的进程
  • cgroup 集成:Landlock 规则可通过 cgroup 节点对整组进程批量应用

十、总结

Landlock 代表了对 Linux 沙盒安全的一次范式转换——从被动依赖管理员配置,到主动由开发者自助实施。其设计优雅地平衡了安全性与易用性:不需要改变文件系统布局、不需要 root 权限、不需要重新编译内核,只需要在启动代码中添加十几行系统调用。

它的分层嵌套架构意味着可以逐步收紧安全策略:父进程定义宽松的范围,子进程在此基础上进一步收缩;容器引擎定义全局策略,容器内应用叠加自己的规则。这种"只能收紧不能放宽"的不可变安全保证,在复杂性日趋增加的 Linux 安全生态中,提供了难得的一致性和可审计性。

当你在 Firefox 中打开一个不可信的文档时,当你在 Flatpak 中访问下载目录时,当你在容器中运行第三方代码时——很可能 Landlock 已经在无声地保护着你。而这正是最好的安全基础设施的模样:无处不在,又毫无感知。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部