• 从 manual 中学习 seccomp 技术


    前言

    限制程序能够使用的系统调用 这篇文章中我描述了使用 seccomp 来限制程序系统调用的技术,虽然能够上手却并不了解它提供的真正功能与实际的应用场景,在本篇文章中探讨下。

    seccomp 的全称是什么?

    seccomp 全称为 Secure Computing,它能够用来限定程序能够执行的系统调用,这一限定可以用于实现沙盒等安全容器,让程序仅能执行受限的内核功能,强化系统的安全性。

    seccomp 的作用范围

    seccomp 配置单位为进程(存疑),它在每一个进程的 task_struct 结构中保存配置信息,这个配置信息在进程 fork 的时候会被子进程继承。

    限制程序能够使用的系统调用 文章中描述的 demo 有如下代码:

    int main(int argc, char const *argv[]) {
      if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
        perror("prctl(NO_NEW_PRIVS)");
        return 1;
      }
      install_filter(__NR_write, AUDIT_ARCH_X86_64, EPERM);
      return system(argv[1]);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    此代码在当前进程中注册了一个 seccomp 的 filter 条件后,使用 system 函数创建子进程执行目标程序来限定子进程不能使用 write 系统调用,它能够工作的基础就是 seccomp 配置能够被【子进程继承】。

    seccomp 的三种工作模式

    SECCOMP_SET_MODE_STRICT

    在此种模式下线程只被允许使用 read、 write, _exit (不包含 exit_group )、与 sigreturn 系统调用,使用其它系统调用时内核会发送 SIGKILL 信号杀死线程。

    此功能需要开启 CONFIG_SECCOMP 内核配置。

    SECCOMP_SET_MODE_FILTER

    此模式允许用户基于 BPF 指令编写过滤规则下发到进程中,扩展了过滤规则的描述元素,同时支持过滤任意的系统调用及系统调用使用的参数。

    要使用 SECCOMP_SET_MODE_FILTER,调用线程所在的命名空间需要有 CAP_SYS_ADMIN 权限、线程设定了 no_new_privs 标志位。当已经设定的 filter 允许线程使用 prctl、seccomp 系统调用时,可以添加更多的过滤规则。多个过滤规则会增加执行时间,但是能够允许进一步降低线程执行过程中可能遇到的攻击。

    要使用 SECCOMP_SET_MODE_FILTER,内核需要开启 CONFIG_SECCOMP_FILTER 配置。

    SECCOMP_GET_ACTION_AVAIL

    此模式用于查询特定的 action 是否被内核支持。这里的 action 表示 seccomp filter 命中后内核执行的行为。

    seccomp bpf filter 的结构与过滤的数据格式

    fitler 信息由如下结构体描述:

               struct sock_fprog {
                   unsigned short      len;    /* Number of BPF instructions */
                   struct sock_filter *filter; /* Pointer to array of
                                                  BPF instructions */
               };
    
    • 1
    • 2
    • 3
    • 4
    • 5

    len 表示 bpf 指令的数量,filter 指向指令存储的数组。sock_filter 结构如下:

               struct sock_filter {            /* Filter block */
                   __u16 code;                 /* Actual filter code */
                   __u8  jt;                   /* Jump true */
                   __u8  jf;                   /* Jump false */
                   __u32 k;                    /* Generic multiuse field */
               };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    当执行 bpf 规则过滤时,bpf 指令码基于内核生成的系统调用信息工作,此信息的定义如下:

              struct seccomp_data {
                  int   nr;                   /* System call number */
                  __u32 arch;                 /* AUDIT_ARCH_* value
                                                 (see ) */
                  __u64 instruction_pointer;  /* CPU instruction pointer */
                  __u64 args[6];              /* Up to 6 system call arguments */
              };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这个结构表明 seccomp filter 功能不仅支持过滤指定的系统调用,也能过滤传递特定参数的系统调用。

    seccomp filter 命中后不同的 action

    seccomp 支持在 filter 规则命中后设定不同的 action,action 按照优先级降低的顺序排列如下:

    • SECCOMP_RET_KILL_PROCESS
    • SECCOMP_RET_KILL_THREAD
    • SECCOMP_RET_TRAP
    • SECCOMP_RET_ERRNO
    • SECCOMP_RET_TRACE
    • SECCOMP_RET_LOG
    • SECCOMP_RET_ALLOW

    当命中多个 action 时,优先级最高的 action 被触发。

    seccomp 的使用场景

    1. 进程沙箱
    2. 监控特定进程执行的系统调用
    3. 过滤进程执行的系统调用
    4. 限定进程能够执行的系统调用

    linux 发行版中有在使用 seccomp 吗?

    在 debian11 发行版中使用 【ftrace】 跟踪 __seccomp_filter函数的调用栈帧,得到了如下信息:

     => 0xffffffffc0c94083
     => __seccomp_filter
     => syscall_trace_enter.constprop.0
     => do_syscall_64
     => entry_SYSCALL_64_after_hwframe
               Timer-11769   [000] ..... 25307.796774: __seccomp_filter <-syscall_trace_enter.constprop.0
               Timer-11769   [000] ..... 25307.796775: 
     => 0xffffffffc0c94083
     => __seccomp_filter
     => syscall_trace_enter.constprop.0
     => do_syscall_64
     => entry_SYSCALL_64_after_hwframe
         Web Content-11763   [003] ..... 25307.796776: __seccomp_filter <-syscall_trace_enter.constprop.0
         Web Content-11763   [003] ..... 25307.796777: 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    11763 在系统中对应的进程信息如下:

    root@debian:/sys/kernel/debug/tracing# ps aux | grep 11763
    longyu     11763  7.2  4.7 3105732 371780 ?      Sl   07:56   3:26 /usr/lib/firefox-esr/firefox-esr -contentproc -childID 18 -isForBrowser -prefsLen 8791 -prefMapSize 246122 -jsInit 285636 -parentBuildID 20220623065118 -appdir /usr/lib/firefox-esr/browser 2093 true tab
    
    • 1
    • 2

    可以看到该进程为火狐浏览器创建的某个进程,表明 seccomp 技术有一定的用户。

    使用 seccomp 让程序无法加载内核模块

    曾经在某次面试的时候被问到如何让程序无法加载内核模块,当时就想到了可以使用 seccomp 来搞,其实可以编写一条过滤 init_module、finit_module 的 seccomp filter,并将 action 设置为不允许访问来完成目标。

    写到这里想到其实上面的描述遗漏了一个关键问题:seccomp filter 要加载到哪个进程中?
    init?systemd?

    其实可以编写一个 pam 模块来添加 filter 并在 /etc/pam.d 中用户的登录配置中增加一条配置来实现。

    libseccomp 库

    上手 seccomp 这篇文章中,示例程序使用如下代码设定一个 filter:

    static int install_filter(int nr, int arch, int error) {
      struct sock_filter filter[] = {
        BPF_STMT(BPF_LD + BPF_W + BPF_ABS, (offsetof(struct seccomp_data, arch))),
        BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch, 0, 3),
        BPF_STMT(BPF_LD + BPF_W + BPF_ABS, (offsetof(struct seccomp_data, nr))),
        BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, nr, 0, 1),
        BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | (error & SECCOMP_RET_DATA)),
        BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW),
      };
      struct sock_fprog prog = {
        .len = (unsigned short)(sizeof(filter) / sizeof(filter[0])),
        .filter = filter,
      };
      if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) {
        perror("prctl(PR_SET_SECCOMP)");
        return 1;
      }
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    上述代码直接使用宏封装的 bpf 指令码编写,代码逻辑难以理解。实际发行版中有提供一个 libseccomp 的三方库,它提供了一些对用户更友好的 api,可以使用这个库来实现 seccomp filter 规则。

    seccomp bpf 支持的架构

    • x86-64, i386, x32 (since Linux 3.5)
    • ARM (since Linux 3.8)
    • s390 (since Linux 3.8)
    • MIPS (since Linux 3.16)
    • ARM-64 (since Linux 3.19)
    • PowerPC (since Linux 4.3)
    • Tile (since Linux 4.3)
    • PA-RISC (since Linux 4.6)

    seccomp bpf filter 的两种运行模式

    1. jit
    2. vm

    这两种方式隐含在 bpf 框架中,对 seccomp 不可见。

    参考链接

    System call filtering and no_new_privs

  • 相关阅读:
    phtread_cancel函数用于取消线程,但不是实时的
    spring事务失效场景
    NAS文件的名称或路径过长导致文件同步被挂起
    C++前缀和算法应用:矩形区域不超过 K 的最大数值和
    php无字母数字rce绕过基础操作
    朗强:高清视频HDMI延长器的特点
    为什么说新一代BI是“面向业务的可视化分析工具”?
    《深度学习推荐系统》读书笔记
    Vue.js 框架源码与进阶 - Vue.js 3.0 Vite 实现原理
    Python | eval、exec | NameError: name ‘XXX‘ is not defined`
  • 原文地址:https://blog.csdn.net/Longyu_wlz/article/details/126239900