Seccomp、BPF与容器安全
字数 1892 2025-08-27 12:33:43

Seccomp、BPF与容器安全 - 教学文档

1. Seccomp简介

Seccomp(全称secure computing mode)是Linux内核支持的一种安全机制,用于限制进程可用的系统调用(system call)。通过Seccomp,我们可以限制程序使用某些系统调用,从而减少系统的暴露面,提高安全性。

1.1 Seccomp发展历史

  • 2005年(Linux 2.6.12):引入第一个版本的seccomp,通过/proc/PID/seccomp接口启用,仅支持严格模式(strict mode),只允许4种系统调用:read()write()_exit()sigreturn()

  • 2007年(Linux 2.6.23):使用prctl()操作代替/proc/PID/seccomp接口

  • 2012年(Linux 3.5):引入"seccomp mode 2"(过滤模式),使用BPF程序过滤系统调用及其参数

  • 2013年(Linux 3.8):在/proc/PID/status中添加Seccomp字段,可查看进程的seccomp模式状态

  • 2014年(Linux 3.17):引入seccomp()系统调用,提供prctl()的超集功能

2. Seccomp模式

2.1 严格模式(SECCOMP_MODE_STRICT)

只允许4种系统调用:

  • read()
  • write()
  • _exit()
  • sigreturn()

示例代码:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <linux/seccomp.h>

void configure_seccomp() {
    printf("Configuring seccomp\n");
    prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);
}

int main(int argc, char *argv[]) {
    int infd, outfd;
    ssize_t read_bytes;
    char buffer[1024];
    
    if(argc < 3) {
        printf("Usage:\n\t dup_file <input path> <output_path>\n");
        return -1;
    }
    
    configure_seccomp(); /* 配置seccomp */
    
    printf("Opening '%s' for reading\n", argv[1]);
    if((infd = open(argv[1], O_RDONLY)) > 0) {
        /* open()被禁用,进程会在此终止*/
        printf("Opening '%s' for writing\n", argv[2]);
        if((outfd = open(argv[2], O_WRONLY|O_CREAT, 0644)) > 0) {
            while((read_bytes = read(infd, &buffer, 1024)) > 0)
                write(outfd, &buffer, (ssize_t)read_bytes);
        }
    }
    close(infd);
    close(outfd);
    return 0;
}

2.2 过滤模式(SECCOMP_MODE_FILTER)

使用BPF程序过滤系统调用,可以基于系统调用号和参数值进行过滤。

3. BPF(伯克利包过滤器)

BPF最初是为网络数据包过滤设计的,后来被应用到seccomp中。

3.1 BPF特性

  • 简单指令集
  • 小型指令集
  • 所有指令大小相同
  • 实现简单、快速
  • 只有分支向前指令
  • 程序是有向无环图(DAGs),没有循环
  • 易于验证程序的有效性/安全性
  • 程序限制为4096条指令

3.2 BPF数据结构

struct sock_fprog {
    unsigned short len;    /* BPF指令的数量 */
    struct sock_filter __user *filter; /* 指向BPF数组的指针 */
};

struct sock_filter {
    __u16 code;     /* 实际过滤代码 */
    __u8 jt;        /* Jump true */
    __u8 jf;        /* Jump false */
    __u32 k;        /* 通用多用途字段 */
};

3.3 BPF指令集

主要指令类型:

  • 加载指令
  • 存储指令
  • 跳转指令
  • 算术逻辑指令(ADD、SUB、MUL、DIV、MOD、NEG、OR、AND、XOR、LSH、RSH)
  • Return指令
  • 条件跳转指令

3.4 BPF编写宏

#define BPF_STMT(code, k) { (unsigned short)(code), 0, 0, k }
#define BPF_JUMP(code, k, jt, jf) { (unsigned short)(code), jt, jf, k }

4. Seccomp + BPF实现

4.1 Seccomp返回值

Seccomp过滤器返回一个32位值:

  • 高16位(SECCOMP_RET_ACTION掩码):指定内核应采取的操作
  • 低16位(SECCOMP_RET_DATA掩码):与操作关联的数据

返回值定义:

#define SECCOMP_RET_KILL_PROCESS 0x80000000U /* kill the process */
#define SECCOMP_RET_KILL_THREAD  0x00000000U /* kill the thread */
#define SECCOMP_RET_KILL         SECCOMP_RET_KILL_THREAD
#define SECCOMP_RET_TRAP        0x00030000U /* disallow and force a SIGSYS */
#define SECCOMP_RET_ERRNO       0x00050000U /* returns an errno */
#define SECCOMP_RET_USER_NOTIF  0x7fc00000U /* notifies userspace */
#define SECCOMP_RET_TRACE       0x7ff00000U /* pass to a tracer or disallow */
#define SECCOMP_RET_LOG         0x7ffc0000U /* allow after logging */
#define SECCOMP_RET_ALLOW       0x7fff0000U /* allow */

4.2 Seccomp输入参数

struct seccomp_data {
    int nr;                   /* 系统调用号 */
    __u32 arch;               /* 架构 */
    __u64 instruction_pointer; /* CPU指令指针 */
    __u64 args[6];            /* 系统调用参数(最多6个) */
};

4.3 使用prctl()实现Seccomp-BPF

#include <stdio.h>
#include <sys/prctl.h>
#include <linux/seccomp.h>
#include <linux/filter.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    struct sock_filter filter[] = {
        BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 0),  // 加载系统调用号
        BPF_JUMP(BPF_JMP + BPF_JEQ, 59, 0, 1),   // 判断是否为execve(59)
        BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_KILL), // 返回KILL
        BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW), // 返回ALLOW
    };
    
    struct sock_fprog prog = {
        .len = (unsigned short)(sizeof(filter)/sizeof(filter[0])),
        .filter = filter,
    };
    
    prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); // 设置NO_NEW_PRIVS
    prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog);
    
    write(0, "test\n", 5);
    system("/bin/sh");
    return 0;
}

4.4 更复杂的过滤示例

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stddef.h>
#include <sys/prctl.h>
#include <linux/seccomp.h>
#include <linux/filter.h>
#include <linux/unistd.h>

void configure_seccomp() {
    struct sock_filter filter[] = {
        BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, nr))),
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_write, 0, 1),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_open, 0, 3),
        BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, args[1]))),
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, O_RDONLY, 0, 1),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL)
    };
    
    struct sock_fprog prog = {
        .len = (unsigned short)(sizeof(filter)/sizeof(filter[0])),
        .filter = filter,
    };
    
    printf("Configuring seccomp\n");
    prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
    prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog);
}

5. libseccomp库

libseccomp提供了更高级的API来使用seccomp,无需直接编写BPF规则。

5.1 基本使用

#include <unistd.h>
#include <seccomp.h>
#include <linux/seccomp.h>

int main(void) {
    scmp_filter_ctx ctx;
    ctx = seccomp_init(SCMP_ACT_ALLOW);
    seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve), 0);
    seccomp_load(ctx);
    
    char *filename = "/bin/sh";
    char *argv[] = {"/bin/sh", NULL};
    char *envp[] = {NULL};
    
    write(1, "i will give you a shell\n", 24);
    syscall(59, filename, argv, envp); //execve
    return 0;
}

5.2 参数过滤

#include <unistd.h>
#include <seccomp.h>
#include <linux/seccomp.h>

int main(void) {
    scmp_filter_ctx ctx;
    ctx = seccomp_init(SCMP_ACT_ALLOW);
    seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(write), 1, SCMP_A2(SCMP_CMP_GT, 0x10));
    seccomp_load(ctx);
    
    write(1, "1234567812345678", 0x10); // 不被拦截
    write(1, "i will give you a shell\n", 24); // 会拦截
    return 0;
}

6. Seccomp与容器安全

Docker支持使用seccomp来限制容器的系统调用。

6.1 Docker默认seccomp配置

Docker使用默认的seccomp配置文件(JSON格式),禁用了大约44个系统调用。

6.2 自定义seccomp配置文件

示例:禁止容器创建文件夹

{
    "defaultAction": "SCMP_ACT_ALLOW",
    "syscalls": [
        {
            "name": "mkdir",
            "action": "SCMP_ACT_ERRNO",
            "args": []
        }
    ]
}

使用方式:

docker run --rm -it --security-opt seccomp=seccomp_mkdir.json busybox /bin/sh

6.3 白名单模式

{
    "defaultAction": "SCMP_ACT_ERRNO",
    "architectures": [
        "SCMP_ARCH_X86_64",
        "SCMP_ARCH_X86",
        "SCMP_ARCH_X32"
    ],
    "syscalls": [
        {
            "names": [
                "arch_prctl",
                "sched_yield",
                "futex",
                "write",
                "mmap",
                "exit_group",
                "madvise",
                "rt_sigprocmask",
                "getpid",
                "gettid",
                "tgkill",
                "rt_sigaction",
                "read",
                "getpgrp"
            ],
            "action": "SCMP_ACT_ALLOW",
            "args": [],
            "comment": "",
            "includes": {},
            "excludes": {}
        }
    ]
}

7. 工具

7.1 seccomp-tools

用于分析seccomp的开源工具,功能包括:

  • Dump:从可执行文件中转储seccomp BPF
  • Disasm:将seccomp BPF转换为可读格式
  • Asm:编写seccomp规则
  • Emu:模拟seccomp规则

安装:

sudo apt install gcc ruby-dev
gem install seccomp-tools

使用示例:

seccomp-tools dump ./simple_syscall_seccomp

7.2 zaz

自动生成JSON格式seccomp文件的工具。

使用示例:

zaz seccomp docker alpine "ping -c5 8.8.8.8" > seccomp_ping.json

7.3 sysdig

系统可见性工具,可用于监控容器运行时使用的系统调用。

安装:

curl -s https://download.sysdig.com/stable/install-sysdig | sudo bash

使用示例:

sysdig -w runc.scap container.name=ping && proc.name=runc

8. 总结

Seccomp是Linux内核提供的重要安全机制,通过限制进程可用的系统调用,可以有效减少系统的暴露面。结合BPF可以实现灵活的系统调用过滤策略。在容器环境中,合理配置seccomp可以显著提高容器的安全性。

关键点:

  1. Seccomp有两种模式:严格模式和过滤模式
  2. 过滤模式使用BPF程序进行系统调用过滤
  3. 可以通过prctl()或libseccomp库配置seccomp
  4. Docker支持自定义seccomp配置文件
  5. 使用seccomp-tools等工具可以分析和生成seccomp策略
Seccomp、BPF与容器安全 - 教学文档 1. Seccomp简介 Seccomp(全称secure computing mode)是Linux内核支持的一种安全机制,用于限制进程可用的系统调用(system call)。通过Seccomp,我们可以限制程序使用某些系统调用,从而减少系统的暴露面,提高安全性。 1.1 Seccomp发展历史 2005年(Linux 2.6.12) :引入第一个版本的seccomp,通过 /proc/PID/seccomp 接口启用,仅支持严格模式(strict mode),只允许4种系统调用: read() 、 write() 、 _exit() 和 sigreturn() 2007年(Linux 2.6.23) :使用 prctl() 操作代替 /proc/PID/seccomp 接口 2012年(Linux 3.5) :引入"seccomp mode 2"(过滤模式),使用BPF程序过滤系统调用及其参数 2013年(Linux 3.8) :在 /proc/PID/status 中添加Seccomp字段,可查看进程的seccomp模式状态 2014年(Linux 3.17) :引入 seccomp() 系统调用,提供 prctl() 的超集功能 2. Seccomp模式 2.1 严格模式(SECCOMP_ MODE_ STRICT) 只允许4种系统调用: read() write() _exit() sigreturn() 示例代码: 2.2 过滤模式(SECCOMP_ MODE_ FILTER) 使用BPF程序过滤系统调用,可以基于系统调用号和参数值进行过滤。 3. BPF(伯克利包过滤器) BPF最初是为网络数据包过滤设计的,后来被应用到seccomp中。 3.1 BPF特性 简单指令集 小型指令集 所有指令大小相同 实现简单、快速 只有分支向前指令 程序是有向无环图(DAGs),没有循环 易于验证程序的有效性/安全性 程序限制为4096条指令 3.2 BPF数据结构 3.3 BPF指令集 主要指令类型: 加载指令 存储指令 跳转指令 算术逻辑指令(ADD、SUB、MUL、DIV、MOD、NEG、OR、AND、XOR、LSH、RSH) Return指令 条件跳转指令 3.4 BPF编写宏 4. Seccomp + BPF实现 4.1 Seccomp返回值 Seccomp过滤器返回一个32位值: 高16位(SECCOMP_ RET_ ACTION掩码):指定内核应采取的操作 低16位(SECCOMP_ RET_ DATA掩码):与操作关联的数据 返回值定义: 4.2 Seccomp输入参数 4.3 使用prctl()实现Seccomp-BPF 4.4 更复杂的过滤示例 5. libseccomp库 libseccomp提供了更高级的API来使用seccomp,无需直接编写BPF规则。 5.1 基本使用 5.2 参数过滤 6. Seccomp与容器安全 Docker支持使用seccomp来限制容器的系统调用。 6.1 Docker默认seccomp配置 Docker使用默认的seccomp配置文件(JSON格式),禁用了大约44个系统调用。 6.2 自定义seccomp配置文件 示例:禁止容器创建文件夹 使用方式: 6.3 白名单模式 7. 工具 7.1 seccomp-tools 用于分析seccomp的开源工具,功能包括: Dump:从可执行文件中转储seccomp BPF Disasm:将seccomp BPF转换为可读格式 Asm:编写seccomp规则 Emu:模拟seccomp规则 安装: 使用示例: 7.2 zaz 自动生成JSON格式seccomp文件的工具。 使用示例: 7.3 sysdig 系统可见性工具,可用于监控容器运行时使用的系统调用。 安装: 使用示例: 8. 总结 Seccomp是Linux内核提供的重要安全机制,通过限制进程可用的系统调用,可以有效减少系统的暴露面。结合BPF可以实现灵活的系统调用过滤策略。在容器环境中,合理配置seccomp可以显著提高容器的安全性。 关键点: Seccomp有两种模式:严格模式和过滤模式 过滤模式使用BPF程序进行系统调用过滤 可以通过prctl()或libseccomp库配置seccomp Docker支持自定义seccomp配置文件 使用seccomp-tools等工具可以分析和生成seccomp策略