<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    Seccomp BPF與容器安全

    VSole2022-07-17 10:07:03

    本文詳細介紹了關于seccomp的相關概念,包括seccomp的發展歷史、Seccomp BPF的實現原理以及與seccomp相關的一些工具等。此外,通過實例驗證了如何使用seccomp bpf 來保護Docker的安全。

    簡介

    seccomp(全稱securecomputing mode)是linux kernel支持的一種安全機制。在Linux系統里,大量的系統調用(systemcall)直接暴露給用戶態程序。但是,并不是所有的系統調用都被需要,而且不安全的代碼濫用系統調用會對系統造成安全威脅。通過seccomp,我們限制程序使用某些系統調用,這樣可以減少系統的暴露面,同時是程序進入一種“安全”的狀態。

    Seccomp 的發展歷史

    2005年,Linux 2.6.12中的引入了第一個版本的seccomp,通過向/proc/PID/seccomp接口中寫入“1”來啟用過濾器,最初只有一個模式:嚴格模式(strict mode),該模式下只允許被限制的進程使用4種系統調用:read(), write(), _exit(), 和 sigreturn() ,需要注意的是,open()系統調用也是被禁止的,這就意味著在進入嚴格模式之前必須先打開文件。一旦為程序施加了嚴格模式的seccomp,對于其他的所有系統調用的調用,都會觸發SIGKILL并立即終止進程。

    2007年,Linux 2.6.23 內核使用prctl()操作代替了/proc/PID/seccomp接口來施加seccomp,通過Prctl (PR_SET_SECCOMP,arg)修改調用者的seccomp模式;prctl(PR_GET_SECCOMP)用來獲取seccomp的狀態,返回值為0時代表進程沒有被施加seccomp,但是如果進程配置了seccomp,則會由于不能調用prctl()導致進程中止,那就沒有其他返回值了?

    2012年,Linux 3.5引入了”seccomp mode 2“,為seccomp帶來了一種新的模式:過濾模式( filter mode ), 該模式使用 Berkeley 包過濾器 (BPF) 程序過濾任意系統調用及其參數,使用該模式,進程可以使用 prctl (PR_SET_SECCOMP, SECCOMP_MODE_FILTER, ...)來指定允許哪些系統調用。現在已經有許多應用使用 seccomp 過濾器來對系統調用進行控制,包括 Chrome/Chromium 瀏覽器、OpenSSH、vsftpd 和 Firefox OS 。

    2013年,Linux 3.8版本,在/proc/PID/status中添加了一個Seccomp字段, 可以通過讀取該文件獲取對應進程的 seccomp 模式的狀態(0 表示禁用,1 表示嚴格,2 表示過濾)。

    /* Valid values for seccomp.mode and prctl(PR_SET_SECCOMP, ) */#define SECCOMP_MODE_DISABLED    0 /* seccomp is not in use. */#define SECCOMP_MODE_STRICT    1 /* uses hard-coded filter. */#define SECCOMP_MODE_FILTER    2 /* uses user-supplied filter. */
    null@ubuntu:~/seccomp$ cat /proc/1/status | grep SeccompSeccomp:        0
    

    2014年,Linux 3.17 引入了seccomp()系統調用,seccomp()在prctl()的基礎上提供了現有功能的超集, 增加了將進程中的所有線程同步到同一組過濾器的能力,這有助于確保即使在施加seccomp過濾器之前創建的線程仍然有效。

    Seccomp + BPF

    seccomp 過濾模式允許開發人員編寫 BPF 程序來確定是否允許給定的系統調用,基于系統調用號和參數(寄存器)值進行過濾。當使用seccomp()或prctl()對進程施加seccomp 時,需要提前將編寫好的BPF程序安裝到內核,之后每次系統調用都會經過該過濾器。而且此過程是不可逆的, 因為安裝過濾器實際上是聲明任何后續執行的代碼都不可信。

    BPF在1992年的tcpdump程序中首次提出,tcpdump是一個網絡數據包的監控工具, 但是由于數據包的數量很大,而且將內核空間捕獲到的數據包傳輸到用戶空間會帶來很多不必要的性能損耗,所以要對數據包進行過濾,只保留感興趣的那一部分,而在內核中過濾感興趣的數據包比在用戶空間中進行過濾更有效。BPF 就是提供了一種進行內核過濾的方法,因此用戶空間只需要處理經過內核過濾的后感興趣的數據包 。

    BPF定義了一個可以在內核內實現的虛擬機(VM)。該虛擬機有以下特性:

    • 簡單指令集
    • 小型指令集
    • 所有的指令大小相同
    • 實現過程簡單、快速
    • 只有分支向前指令
    • 程序是有向無環圖(DAGs),沒有循環
    • 易于驗證程序的有效性/安全性
    • 簡單的指令集?可以驗證操作碼和參數
    • 可以檢測死代碼
    • 程序必須以 Return 結束
    • BPF過濾器程序僅限于4096條指令

    BPF 程序在Linux內核中主要在filter.h和bpf_common.h中實現,主要的數據結構包括以下幾個:

    Linux v5.18.4/include/uapi/linux/filte.h -> sock_fprog(https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/filter.h#L24

    struct sock_fprog {    /* Required for SO_ATTACH_FILTER. */    unsigned short        len;    /* BPF指令的數量 */    struct sock_filter __user *filter;  /*指向BPF數組的指針 */};
    

    這個結構體記錄了過濾規則個數與規則數組起始位置 , 而 filter 域指向了具體的規則,每一條規則的形式如下:

    Linux v5.18.4/include/uapi/linux/filte.h -> sock_filter(https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/filter.h#L24

    struct sock_filter {    /* Filter block */    __u16    code;   /* Actual filter code */    __u8    jt;    /* Jump true */    __u8    jf;    /* Jump false */    __u32    k;      /* Generic multiuse field */};
    

    該規則有四個參數,code:過濾指令;jt:條件真跳轉;jf:條件假跳轉;k:操作數。

    BPF的指令集比較簡單,主要有以下幾個指令:

    • 加載指令
    • 存儲指令
    • 跳轉指令
    • 算術邏輯指令
    • 包括:ADD、SUB、 MUL、 DIV、 MOD、 NEG、OR、 AND、XOR、 LSH、 RSH
    • Return 指令
    • 條件跳轉指令
    • 有兩個跳轉目標,jt為真,jf為假
    • jmp 目標是指令偏移量,最大 255

    如何編寫BPF程序呢?BPF指令可以手工編寫,但是,開發人員定義了符號常量和兩個方便的宏BPF_STMT和BPF_JUMP可以用來方便的編寫BPF規則。

    Linux v5.18.4/include/uapi/linux/filte.h -> BPF_STMT&BPF_JUMP(https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/filter.h#L45

    /* * Macros for filter block array initializers. */#ifndef BPF_STMT#define BPF_STMT(code, k) { (unsigned short)(code), 0, 0, k }#endif#ifndef BPF_JUMP#define BPF_JUMP(code, k, jt, jf) { (unsigned short)(code), jt, jf, k }#endif
    
    • BPF_STMT

    BPF_STMT有兩個參數,操作碼(code)和值(k),舉個例子:

    BPF_STMT(BPF_LD | BPF_W | BPF_ABS,(offsetof(struct seccomp_data, arch)))
    

    這里的操作碼是由三個指令相或組成的,BPF_LD: 建一個 BPF 加載操作 ;BPF_W:操作數大小是一個字,BPF_ABS: 使用絕對偏移,即使用指令中的值作為數據區的偏移量,該值是體系結構字段與數據區域的偏移量 。offsetof()生成數據區域中期望字段的偏移量。

    該指令的功能是將體系架構數加載到累加器中。

    • BPF_JUMP

    BPF_JUMP 中有四個參數:操作碼、值(k)、為真跳轉(jt)和為假跳轉(jf),舉個例子:

    BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K ,AUDIT_ARCH_X86_64 , 1, 0)
    

    BPF_JMP | BPF JEQ會創建一個相等跳轉指令,它將指令中的值(即第二個參數AUDIT_ARCH_X86_64)與累加器中的值(BPF_K)進行比較。判斷是否相等,也就是說,如果架構是 x86-64,則跳過下一條指令(jt=1,代表測試為真跳過一條指令),否則將執行下一條指令(jf=0,代表如果測試為假,則跳過0條指令,也就是繼續執行下一條指令)。

    上面這兩條指令常用作系統架構的驗證。

    再舉個實際例子,該示例用作過濾execve系統調用的過濾規則:

    struct sock_filter filter[] = {    BPF_STMT(BPF_LD+BPF_W+BPF_ABS,0),           //將幀的偏移0處,取4個字節數據,也就是系統調用號的值載入累加器    BPF_JUMP(BPF_JMP+BPF_JEQ,59,0,1),           //當A == 59時,順序執行下一條規則,否則跳過下一條規則,這里的59就是x64的execve系統調用號    BPF_STMT(BPF_RET+BPF_K,SECCOMP_RET_KILL),   //返回KILL    BPF_STMT(BPF_RET+BPF_K,SECCOMP_RET_ALLOW),  //返回ALLOW};
    

    在bpf_common.h(https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/bpf_common.h#L7)中給出了BPF_STMT和BPF_JUMP相關的操作碼:

    /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */#ifndef _UAPI__LINUX_BPF_COMMON_H__#define _UAPI__LINUX_BPF_COMMON_H__
    /* Instruction classes */                    #define BPF_CLASS(code) ((code) & 0x07)    //指定操作的類別#define        BPF_LD        0x00               //將值復制到累加器中#define        BPF_LDX        0x01               //將值加載到索引寄存器中#define        BPF_ST        0x02               //將累加器中的值存到暫存器#define        BPF_STX        0x03               //將索引寄存器的值存儲在暫存器中#define        BPF_ALU        0x04               //用索引寄存器或常數作為操作數在累加器上執行算數或邏輯運算#define        BPF_JMP        0x05               //跳轉#define        BPF_RET        0x06               //返回#define        BPF_MISC        0x07           // 其他類別
    /* ld/ldx fields */#define BPF_SIZE(code)  ((code) & 0x18)#define        BPF_W        0x00 /* 32-bit */       //字#define        BPF_H        0x08 /* 16-bit */       //半字#define        BPF_B        0x10 /*  8-bit */       //字節/* eBPF        BPF_DW        0x18    64-bit */       //雙字#define BPF_MODE(code)  ((code) & 0xe0)#define        BPF_IMM        0x00                  //常數 #define        BPF_ABS        0x20                  //固定偏移量的數據包數據(絕對偏移)#define        BPF_IND        0x40                  //可變偏移量的數據包數據(相對偏移)#define        BPF_MEM        0x60                  //暫存器中的一個字#define        BPF_LEN        0x80                  //數據包長度#define        BPF_MSH        0xa0
    /* alu/jmp fields */#define BPF_OP(code)    ((code) & 0xf0)       //當操作碼類型為ALU時,指定具體運算符   #define        BPF_ADD        0x00        #define        BPF_SUB        0x10#define        BPF_MUL        0x20#define        BPF_DIV        0x30#define        BPF_OR        0x40#define        BPF_AND        0x50#define        BPF_LSH        0x60#define        BPF_RSH        0x70#define        BPF_NEG        0x80#define        BPF_MOD        0x90#define        BPF_XOR        0xa0                                               //當操作碼是jmp時指定跳轉類型#define        BPF_JA        0x00#define        BPF_JEQ        0x10#define        BPF_JGT        0x20#define        BPF_JGE        0x30#define        BPF_JSET        0x40#define BPF_SRC(code)   ((code) & 0x08)#define        BPF_K        0x00                    //常數#define        BPF_X        0x08                    //索引寄存器
    #ifndef BPF_MAXINSNS#define BPF_MAXINSNS 4096#endif
    #endif /* _UAPI__LINUX_BPF_COMMON_H__ */
    

    與seccomp相關的定義大多數在seccomp.h中定義。

    一旦為程序配置了seccomp-BPF,每個系統調用都會經過seccomp過濾器,這在一定程度上會影響系統的性能。此外,Seccomp過濾器會向內核返回一個值,指示是否允許該系統調用,該返回值是一個 32 位的數值,其中最重要的 16 位(SECCOMP_RET_ACTION掩碼)指定內核應該采取的操作,其他位(SECCOMP_RET_DATA 掩碼)用于返回與操作關聯的數據 。

    /* * All BPF programs must return a 32-bit value. * The bottom 16-bits are for optional return data. * The upper 16-bits are ordered from least permissive values to most, * as a signed value (so 0x8000000 is negative). * * The ordering ensures that a min_t() over composed return values always * selects the least permissive choice. */#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 */
    /* Masks for the return value sections. */#define SECCOMP_RET_ACTION_FULL    0xffff0000U#define SECCOMP_RET_ACTION    0x7fff0000U#define SECCOMP_RET_DATA    0x0000ffffU
    
    • SECCOMP_RET_ALLOW:允許執行
    • SECCOMP_RET_KILL:立即終止執行
    • SECCOMP_RET_ERRNO:從系統調用中返回一個錯誤(系統調用不執行)
    • SECCOMP_RET_TRACE:嘗試通知ptrace(), 使之有機會獲得控制權
    • SECCOMP_RET_TRAP:通知內核發送SIGSYS信號(系統調用不執行)

    每一個seccomp-BPF程序都使用seccomp_data(https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/seccomp.h#L63)結構作為輸入參數:

    /include(https://elixir.bootlin.com/linux/latest/source/include

    /uapi(https://elixir.bootlin.com/linux/latest/source/include/uapi

    /linux(https://elixir.bootlin.com/linux/latest/source/include/uapi/linux

    /seccomp.h (https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/seccomp.h):

    struct seccomp_data {  int nr ;                    /* 系統調用號(依賴于體系架構) */  __u32 arch ;                /* 架構(如AUDIT_ARCH_X86_64) */  __u64 instruction_pointer ; /* CPU指令指針 */  __u64 args [6];             /* 系統調用參數,最多有6個參數 */};
    

    實現

    Prctl()

    prctl (https://man7.org/linux/man-pages/man2/prctl.2.html)函數是為進程制定而設計的,該函數原型如下:

    #include 
    int prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5);
    

    其中明確指定哪種種操作在于option選項, option有很多,與seccomp有關的option主要有兩個: PR_SET_NO_NEW_PRIVS()和PR_SET_SECCOMP()。

    PR_SET_NO_NEW_PRIVS():是在Linux 3.5 之后引入的特性,當一個進程或者子進程設置了PR_SET_NO_NEW_PRIVS 屬性,則其不能訪問一些無法共享的操作,如setuid、chroot等。配置seccomp-BPF的程序必須擁有Capabilities 中 的CAP_SYS_ADMIN,或者程序已經定義了no_new_privs屬性。若不這樣做 非 root 用戶使用該程序時 seccomp保護將會失效,設置了 PR_SET_NO_NEW_PRIVS 位后能保證 seccomp 對所有用戶都能起作用。

    prctl(PR_SET_NO_NEW_PRIVS,1,0,0,0);
    

    如果將其第二個參數設置為1,則這個操作能保證seccomp對所有用戶都能起作用,并且會使子進程即execve后的進程依然受到seccomp的限制。

    PR_SET_SECCOMP():為進程設置seccomp;通常的形式如下:

    prctl(PR_SET_SECCOMP,SECCOMP_MODE_FILTER,&prog);
    

    SECCOMP_MODE_FILTER參數表示設置的seccomp的過濾模式,如果設置為SECCOMP_MODE_STRICT,則代表嚴格模式;若為過濾模式,則對應的系統調用限制通過&prog結構體定義(上面提到過的 struct sock_fprog)。

    嚴格模式的簡單示例

    在嚴格模式下,進程可用的系統調用只有4個,因為open()也被禁用,所有在進入嚴格模式前,需要先打開文件,簡單的示例如下:

    seccomp_strict.c:

    #include #include #include #include #include 
    void configure_seccomp() {  printf("Configuring seccomp");  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:\tdup_file  ");    return -1;  }
      configure_seccomp(); /* 配置seccomp */
      printf("Opening '%s' for reading", argv[1]);  if ((infd = open(argv[1], O_RDONLY)) > 0) { /* open() 被禁用,進程會在此終止*/    printf("Opening '%s' for writing", 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;}
    

    代碼功能實現簡單的文件復制,當seccomp施加嚴格模式的時候運行時,seccomp 會在執行open(argv[1], O_RDONLY)函數調用時終止應用程序 。

    null@ubuntu:~/seccomp$ gcc -o seccomp_strict seccomp_strict.cnull@ubuntu:~/seccomp$ ./seccomp_strict /etc/passwd outputConfiguring seccompOpening '/etc/passwd' for readingKilled
    

    過濾模式的簡單示例

    通過上面的介紹和程序流,如果我們想要為一個程序施加seccomp-BPF策略,那可以分為以下幾個步驟,首先定義filter數組,之后定義prog參數,最后使用prctl施加策略。

    示例一:禁止execve系統調用

    seccomp_filter_execv.c:

    #include #include #include #include #include #include int main(){struct sock_filter filter[] = {    BPF_STMT(BPF_LD+BPF_W+BPF_ABS,0), //將幀的偏移0處,取4個字節數據,也就是系統調用號的值載入累加器    BPF_JUMP(BPF_JMP+BPF_JEQ,59,0,1), //判斷系統調用號是否為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",5);    system("/bin/sh");    return 0;}
    

    示例二:

    seccomp_filter.c:

    #include #include #include #include #include #include #include #include 
    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), //測試系統調用號是否匹配'__NR__write',如果是允許其他syscall,如果不是則跳過下一條指令,    BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),    BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_open, 0, 3),//測試是否為'__NR_open',不是直接退出,    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),//判斷是否是'O_RDONLY'的方式,是則允許    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");  prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);  prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog);}
    int main(int argc, char* argv[]) {  int infd, outfd;  ssize_t read_bytes;  char buffer[1024];
      if (argc < 3) {    printf("Usage:\tdup_file  ");    return -1;  }  printf("Ducplicating file '%s' to '%s'", argv[1], argv[2]);
      configure_seccomp(); //配置seccomp
      printf("Opening '%s' for reading", argv[1]);  if ((infd = open(argv[1], O_RDONLY)) > 0) {    printf("Opening '%s' for writing", 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;}
    

    在這種情況下, 在這種情況下,seccomp-BPF 程序將允許使用 O_RDONLY 參數打開第一個調用 , 但是在使用 O_WRONLY | O_CREAT 參數調用 open 時終止程序。

    $ ./seccomp_filter /etc/passwd outputDucplicating file '/etc/passwd' to 'output'Configuring seccompOpening '/etc/passwd' for readingOpening 'output' for writingBad system call
    

    libseccomp

    項目地址:libseccomp:https://github.com/seccomp/libseccomp

    基于prctl()函數的機制不夠靈活,libseccomp庫可以提供一些函數實現prctl類似的效果,庫中封裝了一些函數,可以不用了解BPF規則而實現過濾。但是在c程序中使用它,需要裝一些庫文件:

    null@ubuntu:~/seccomp$ sudo apt install libseccomp-dev libseccomp2 seccomp
    

    使用示例:

    simple_syscall_seccomp.c:

    //gcc -g simple_syscall_seccomp.c -o simple_syscall_seccomp -lseccomp#include #include #include 
    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",24);    syscall(59,filename,argv,envp);//execve    return 0;}
    

    編譯運行, 在執行 execve 時程序報錯退出 :

    null@ubuntu:~/seccomp$ gcc -g simple_syscall_seccomp.c -o simple_syscall_seccomp -lseccompnull@ubuntu:~/seccomp$ ./simple_syscall_seccompi will give you a shellBad system call (core dumped)
    

    解釋一下上訴代碼:

    scmp_filter_ctx : 過濾器的結構體

    seccomp_init : 初始化的過濾狀態 ,函數原型:

    seccomp_init(uint32_t def_action)
    

    可選的def_action有:

    SCMP_ACT_ALLOW:即初始化為允許所有系統調用,過濾為黑名單模式;SCMP_ACT_KILL:則為白名單模式過濾。SCMP_ACT_KILL_PROCESS:整個進程將被內核終止SCMP_ACT_TRAP:如果所有系統調用都不匹配,則給線程發送一個SIGSYS信號SCMP_ACT_TRACE(uint16_t msg_num):在使用ptrace根據進程時的相關選項SCMP_ACT_ERRNO(uint16_t errno):不匹配會收到errno的返回值SCMP_ACT_LOG:不影響系統調用,但是會被記錄;
    

    seccomp_rule_add(https://man7.org/linux/man-pages/man3/seccomp_rule_add.3.html) :添加一條規則,函數原型為:

    int seccomp_rule_add(scmp_filter_ctx ctx, uint32_t action,int syscall, unsigned int arg_cnt, ...);
    

    其中arg_cnt參數表明是否需要對對應系統調用的參數做出限制以及指示做出限制的個數,如果僅僅需要允許或者禁止所有某個系統調用,arg_cnt直接傳入0即可,如 seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve), 0) 即禁用execve,不管其參數如何。如果arg_cnt的參數不為0, 那 arg_cnt 表示后面限制的參數的個數,也就是只有調用 execve,且參數滿足要求時,才會攔截 syscall 。如果想要更細粒度的過濾系統調用,把參數也考慮進去,就要設置arg_cnt不為零,然后在利用宏做一些過濾。

    舉個例子, 攔截 write 函數 參數大于 0x10 時的系統調用 :

    seccomp_write_limit.c:

    #include #include #include 
    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));//第2(從0)個參數大于0x10    seccomp_load(ctx);    write(1,"1234567812345678",0x10);//不被攔截    write(1,"i will give you a shell",24);//會攔截       return 0;}
    

    編譯執行

    null@ubuntu:~/seccomp$ gcc -g seccomp_write_limit.c -o seccomp_write_limit -lseccompnull@ubuntu:~/seccomp$ ./seccomp_write_limit1234567812345678Bad system call (core dumped)
    

    其中SCMP_A2代表為第二個參數指定比較結構,SCMP_CMP_GT代表 大于(greater than),詳細內容如下。

    libseccmop/include/seccomp.h.in(https://github.com/seccomp/libseccomp/blob/3f0e47fe2717b73ccef68ca18f9f7297ee73ebb2/include/seccomp.h.in):

    ......
    /** * Comparison operators */enum scmp_compare {    _SCMP_CMP_MIN = 0,    SCMP_CMP_NE = 1,        /**< not equal */    SCMP_CMP_LT = 2,        /**< less than */    SCMP_CMP_LE = 3,        /**< less than or equal */    SCMP_CMP_EQ = 4,        /**< equal */    SCMP_CMP_GE = 5,        /**< greater than or equal */    SCMP_CMP_GT = 6,        /**< greater than */    SCMP_CMP_MASKED_EQ = 7,        /**< masked equality */    _SCMP_CMP_MAX,}; ... struct scmp_arg_cmp {    unsigned int arg;    /**< argument number, starting at 0 */    enum scmp_compare op;    /**< the comparison op, e.g. SCMP_CMP_* */    scmp_datum_t datum_a;    scmp_datum_t datum_b;}; ..../** * Specify a 32-bit argument comparison struct for use in declaring rules * @param arg the argument number, starting at 0 * @param op the comparison operator, e.g. SCMP_CMP_* * @param datum_a dependent on comparison (32-bits) * @param datum_b dependent on comparison, optional (32-bits) */#define SCMP_CMP32(x, y, ...) \    _SCMP_MACRO_DISPATCHER(_SCMP_CMP32_, __VA_ARGS__)(x, y, __VA_ARGS__)
    /** * Specify a 64-bit argument comparison struct for argument 0 */#define SCMP_A0_64(...)        SCMP_CMP64(0, __VA_ARGS__)#define SCMP_A0            SCMP_A0_64
    /** * Specify a 32-bit argument comparison struct for argument 0 */#define SCMP_A0_32(x, ...)    SCMP_CMP32(0, x, __VA_ARGS__)
    /** * Specify a 64-bit argument comparison struct for argument 1 */#define SCMP_A1_64(...)        SCMP_CMP64(1, __VA_ARGS__)#define SCMP_A1            SCMP_A1_64
    /** * Specify a 32-bit argument comparison struct for argument 1 */#define SCMP_A1_32(x, ...)    SCMP_CMP32(1, x, __VA_ARGS__)
    /** * Specify a 64-bit argument comparison struct for argument 2 */#define SCMP_A2_64(...)        SCMP_CMP64(2, __VA_ARGS__)#define SCMP_A2            SCMP_A2_64
    /** * Specify a 32-bit argument comparison struct for argument 2 */#define SCMP_A2_32(x, ...)    SCMP_CMP32(2, x, __VA_ARGS__)
    /** * Specify a 64-bit argument comparison struct for argument 3 */#define SCMP_A3_64(...)        SCMP_CMP64(3, __VA_ARGS__)#define SCMP_A3            SCMP_A3_64
    /** * Specify a 32-bit argument comparison struct for argument 3 */#define SCMP_A3_32(x, ...)    SCMP_CMP32(3, x, __VA_ARGS__)
    /** * Specify a 64-bit argument comparison struct for argument 4 */#define SCMP_A4_64(...)        SCMP_CMP64(4, __VA_ARGS__)#define SCMP_A4            SCMP_A4_64
    /** * Specify a 32-bit argument comparison struct for argument 4 */#define SCMP_A4_32(x, ...)    SCMP_CMP32(4, x, __VA_ARGS__)
    /** * Specify a 64-bit argument comparison struct for argument 5 */#define SCMP_A5_64(...)        SCMP_CMP64(5, __VA_ARGS__)#define SCMP_A5            SCMP_A5_64
    /** * Specify a 32-bit argument comparison struct for argument 5 */#define SCMP_A5_32(x, ...)    SCMP_CMP32(5, x, __VA_ARGS__)
         ...     ...
    

    除了seccomp_rule_add之外,還有其他添加規則的函數,如:seccomp_rule_add_array ()、 seccomp_rule_add_exact ()和seccomp_rule_add_exact_array (),詳細信息可查看參考鏈接。

    seccomp_load:將當前的 seccomp 過濾器加載到內核中 ,函數原型:

    int seccomp_load(scmp_filter_ctx ctx);
    

    seccomp_reset : 釋放現有的過濾上下文重新初始化之前的狀態,并且只能在成功調用seccomp_init () 之后才能使用。

    int seccomp_reset(scmp_filter_ctx ctx ,uint32_t def_action )
    

    其他工具

    seccmop-bpf.h

    seccomp-bpf.h(https://github.com/ahupowerdns/secfilter/blob/master/seccomp-bpf.h)是由開發人員編寫的一個十分便捷的頭文件用于開發seccomp-bpf 。該頭文件已經定義好了很多常見的宏,如驗證系統架構、允許系統調用等功能,十分便捷,如下所示。

    ...define VALIDATE_ARCHITECTURE \    BPF_STMT(BPF_LD+BPF_W+BPF_ABS, arch_nr), \    BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ARCH_NR, 1, 0), \    BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL)
    define EXAMINE_SYSCALL \    BPF_STMT(BPF_LD+BPF_W+BPF_ABS, syscall_nr)
    define ALLOW_SYSCALL(name) \    BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_##name, 0, 1), \    BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW)
    define KILL_PROCESS \    BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL)...
    

    應用示例:

    seccomp_policy.c(https://gist.github.com/mstemm/1bc06c52abb7b6b4feef79d7bfff5815#file-seccomp_policy-c

    #include #include #include #include #include #include #include #include "seccomp-bpf.h"
    void install_syscall_filter(){        struct sock_filter filter[] = {                /* Validate architecture. */                VALIDATE_ARCHITECTURE,                /* Grab the system call number. */                EXAMINE_SYSCALL,                /* List allowed syscalls. We add open() to the set of                   allowed syscalls by the strict policy, but not                   close(). */                ALLOW_SYSCALL(rt_sigreturn),#ifdef __NR_sigreturn                ALLOW_SYSCALL(sigreturn),#endif                ALLOW_SYSCALL(exit_group),                ALLOW_SYSCALL(exit),                ALLOW_SYSCALL(read),                ALLOW_SYSCALL(write),                ALLOW_SYSCALL(open),                KILL_PROCESS,        };        struct sock_fprog prog = {                .len = (unsigned short)(sizeof(filter)/sizeof(filter[0])),                .filter = filter,        };
            assert(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == 0);
            assert(prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) == 0);}
    int main(int argc, char **argv){        int output = open("output.txt", O_WRONLY);        const char *val = "test";
            printf("Calling prctl() to set seccomp with filter...");
            install_syscall_filter();
            printf("Writing to an already open file...");        write(output, val, strlen(val)+1);
            printf("Trying to open file for reading...");        int input = open("output.txt", O_RDONLY);
            printf("Note that open() worked. However, close() will not");        close(input);
            printf("You will not see this message--the process will be killed first");}
    

    執行結果

    $ ./seccomp_policyCalling prctl() to set seccomp with filter...Writing to an already open file...Trying to open file for reading...Note that open() worked. However, close() will notBad system call
    

    seccomp-tools

    一款用于分析seccomp的開源工具,項目地址:https://github.com/david942j/seccomp-tools

    主要功能:

    • Dump:從可執行文件中自動轉儲 seccomp BPF
    • Disasm:將 seccomp BPF 轉換為人類可讀的格式
    • Asm:使編寫seccomp規則類似于編寫代碼
    • Emu:模擬 seccomp 規則

    安裝

    sudo apt install gcc ruby-devgem install seccomp-tools
    

    使用

    null@ubuntu:~/seccomp$ seccomp-tools dump ./simple_syscall_seccomp line  CODE  JT   JF      K================================= 0000: 0x20 0x00 0x00 0x00000004  A = arch 0001: 0x15 0x00 0x05 0xc000003e  if (A != ARCH_X86_64) goto 0007 0002: 0x20 0x00 0x00 0x00000000  A = sys_number 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x02 0xffffffff  if (A != 0xffffffff) goto 0007 0005: 0x15 0x01 0x00 0x0000003b  if (A == execve) goto 0007 0006: 0x06 0x00 0x00 0x7fff0000  return ALLOW 0007: 0x06 0x00 0x00 0x00000000  return KILL
    

    從輸出中可知禁用了execve系統調用。

    使用Seccomp保護Docker的安全

    Seccomp技術被用在很多應用程序上以保護系統的安全性,Docker支持使用seccomp來限制容器的系統調用,不過需要啟用內核中的CONFIG_SECCOMP。

    null@ubuntu:~$ grep CONFIG_SECCOMP= /boot/config-$(uname -r)CONFIG_SECCOMP=y
    

    當使用docker run 啟動一個容器時,Docker會使用默認的seccomp配置文件來對容器施加限制策略,該默認文件是以json格式編寫, 在 300 多個系統調用中禁用了大約 44 個系統調用,可以在Moby項目中找到該源碼(https://github.com/moby/moby/blob/master/profiles/seccomp/default.json)。

    null@ubuntu:~$ sudo docker run --rm -it ubuntu /bin/bashroot@85e01c28bd2c:/# bashroot@85e01c28bd2c:/# ps   PID TTY          TIME CMD     1 pts/0    00:00:00 bash    10 pts/0    00:00:00 bash    13 pts/0    00:00:00 psroot@85e01c28bd2c:/# grep -i seccomp /proc/1/statusSeccomp:        2
    

    Docker中默認的配置文件提供了最大限度的包容性,除了默認的選擇之外,Docker允許我們自定義該配置文件來靈活的對容器的系統調用進行限制。

    示例:以白名單的形式允許特定的系統調用

    example.json

    {    "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": {}        }    ]}
    

    defaultAction : 指定默認的seccomp 操作,具體的可選參數上面已經介紹過了,最常用的無非是SCMP_ACT_ALLOW、SCMP_ACT_ERRNO,這里選擇SCMP_ACT_ERRNO,表示默認禁止全部系統調用,以白名單的形式在賦予可用的系統調用。

    architectures :系統架構,不同的系統架構系統調用可能不同。

    syscalls:指定系統調用以及對應的操作,name定義系統調用名,action對應的操作,這里表示允許name里邊中的系統調用,args對應系統調用參數,可以為空。

    這樣,在使用 docker run 運行容器時,就可以使用 --security-opt 選項指定該配置文件來對容器進行系統調用定制。

    $ docker run --rm -it --security-opt seccomp=/path/to/seccomp/example.json hello-world
    

    舉例,禁止容器創建文件夾,就可以用黑名單的形式禁用mkdir系統調用

    seccomp_mkdir.json:

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

    使用該策略啟動容器,并在容器中創建文件夾時,就會收到禁止信息,不允許創建文件夾。

    null@ubuntu:~/seccomp/docker$ sudo docker run --rm -it --security-opt seccomp=seccomp_mkdir.json busybox /bin/sh/ # lsbin   dev   etc   home  proc  root  sys   tmp   usr   var/ # mkdir testmkdir: can't create directory 'test': Operation not permitted
    

    當然也可以不適用任何seccomp策略啟動容器,只需要在啟動選項中加上--security-opt seccomp=unconfined即可。

    zaz

    zaz seccomp 是一個可以為容器自動生成json格式的seccomp文件的開源工具,項目地址:https://github.com/pjbgf/zaz。 

    主要用法為

    zaz seccomp docker IMAGE COMMAND
    

    它能夠為特定的可執行文件定制系統調用,以只允許特定的操作,禁止其他操作。

    舉個例子:為alpine中的ping命令生成seccomp配置文件

    $ sudo ./zaz seccomp docker alpine "ping -c5 8.8.8.8" > seccomp_ping.json$ cat seccomp_ping.json | jq '.'{  "defaultAction": "SCMP_ACT_ERRNO",  "architectures": [    "SCMP_ARCH_X86_64",    "SCMP_ARCH_X86",    "SCMP_ARCH_X32"  ],  "syscalls": [    {      "names": [        "arch_prctl",        "bind",        "clock_gettime",        "clone",        "close",        "connect",        "dup2",        "epoll_pwait",        "execve",        "exit",        "exit_group",        "fcntl",        "futex",        "getpid",        "getsockname",        "getuid",        "ioctl",        "mprotect",        "nanosleep",        "open",        "poll",        "read",        "recvfrom",        "rt_sigaction",        "rt_sigprocmask",        "rt_sigreturn",        "sendto",        "set_tid_address",        "setitimer",        "setsockopt",        "socket",        "write",        "writev"      ],      "action": "SCMP_ACT_ALLOW"    }  ]}
    

    如上所示,zaz檢測到了33個系統調用,使用白名單的形式過濾系統調用。那它以白名單的形式生成的系統調用能否很好的過濾系統系統呢?是否能夠滿足運行ping命令,而不能運行除了它允許的系統調用之外的命令呢?做個測試,首先用下面Dockerfile構建一個簡單的鏡像。

    Dockerfile

    FROM alpine:latestCMD ["ping","-c5","8.8.8.8"]
    

    構建成功后,使用默認的seccomp策略啟動容器,沒有任何問題,可以運行。

    $ sudo docker build -t pingtest .$ sudo docker run --rm -it pingtestPING 8.8.8.8 (8.8.8.8): 56 data bytes64 bytes from 8.8.8.8: seq=0 ttl=127 time=42.139 ms64 bytes from 8.8.8.8: seq=1 ttl=127 time=42.646 ms64 bytes from 8.8.8.8: seq=2 ttl=127 time=42.098 ms64 bytes from 8.8.8.8: seq=3 ttl=127 time=42.484 ms64 bytes from 8.8.8.8: seq=4 ttl=127 time=42.007 ms
    --- 8.8.8.8 ping statistics ---5 packets transmitted, 5 packets received, 0% packet lossround-trip min/avg/max = 42.007/42.274/42.646 ms
    

    接著我們使用上述zaz生成的策略試試:

    $ sudo docker run --rm -it --security-opt seccomp=seccomp_ping.json pingtestdocker: Error response from daemon: failed to create shim: OCI runtime create failed: container_linux.go:380: starting container process caused: close exec fds: open /proc/self/fd: operation not permitted: unknown.
    

    容器并沒有成功啟動,在創建OCI的時候就報錯了,報錯原因是operation not permitted,這個報錯上面似乎提到過,是想要使用的系統調用被禁用的緣故,可能zaz這種白名單的模式魯棒性還是不夠強,而且Docker更新那么多次,zaz缺乏維護導致捕獲的系統調用不足,在容器啟動過程中出現了問題。奇怪的是,當我在此運行同樣的命令,卻引發了panic報錯:No error following JSON procError payload。

    $ sudo docker run --rm -it --security-opt seccomp=seccomp_ping.json pingtestdocker: Error response from daemon: failed to create shim: OCI runtime create failed: runc did not terminate successfully: exit status 2: panic: No error following JSON procError payload.
    goroutine 1 [running]:github.com/opencontainers/runc/libcontainer.parseSync(0x56551adf30b8, 0xc000010b20, 0xc0002268a0, 0xc00027f9e0, 0x0)        github.com/opencontainers/runc/libcontainer/sync.go:93 +0x307github.com/opencontainers/runc/libcontainer.(*initProcess).start(0xc000297cb0, 0x0, 0x0)        github.com/opencontainers/runc/libcontainer/process_linux.go:440 +0x5efgithub.com/opencontainers/runc/libcontainer.(*linuxContainer).start(0xc000078700, 0xc000209680, 0x0, 0x0)        github.com/opencontainers/runc/libcontainer/container_linux.go:379 +0xf5github.com/opencontainers/runc/libcontainer.(*linuxContainer).Start(0xc000078700, 0xc000209680, 0x0, 0x0)        github.com/opencontainers/runc/libcontainer/container_linux.go:264 +0xb4main.(*runner).run(0xc0002274c8, 0xc0000200f0, 0x0, 0x0, 0x0)        github.com/opencontainers/runc/utils_linux.go:312 +0xd2amain.startContainer(0xc00025c160, 0xc000076400, 0x1, 0x0, 0x0, 0xc0002275b8, 0x6)        github.com/opencontainers/runc/utils_linux.go:455 +0x455main.glob..func2(0xc00025c160, 0xc000246000, 0xc000246120)        github.com/opencontainers/runc/create.go:65 +0xbbgithub.com/urfave/cli.HandleAction(0x56551ad3b040, 0x56551ade81e8, 0xc00025c160, 0xc00025c160, 0x0)        github.com/urfave/cli@v1.22.1/app.go:523 +0x107github.com/urfave/cli.Command.Run(0x56551aa566f5, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x56551aa5f509, 0x12, 0x0, ...)        github.com/urfave/cli@v1.22.1/command.go:174 +0x579github.com/urfave/cli.(*App).Run(0xc000254000, 0xc000132000, 0xf, 0xf, 0x0, 0x0)        github.com/urfave/cli@v1.22.1/app.go:276 +0x7e8main.main()        github.com/opencontainers/runc/main.go:163 +0xd3f: unknown.
    

    這種報錯或許是不應該的,我嘗試在網上尋找報錯的相關信息,類似的情況很少,而且并不是每次運行都是出現這種panic,正常情況下應該是operation not permitted,這是由于我們的白名單沒有完全包括必須的系統調用導致的。目前將此情況匯報給了Moby issue(https://github.com/moby/moby/issues/43730),或許能夠得到一些解答。

    類似panic信息:

    https://bugzilla.redhat.com/show_bug.cgi?format=multiple&id=1714183

    無論是哪種報錯,看起來都是runc出了問題,嘗試解決這個問題,我們就要知道Docker到底是如何在運行時加載seccomp?

    當我們要創建一個容器的時候 ,容器守護進程 Dockerd會請求 containerd 來創建一個容器 , containerd 收到請求后,也并不會直接去操作容器,而是創建一個叫做 containerd-shim 的進程,讓這個進程去操作容器,之后containerd-shim會通過OCI去調用容器運行時runc來啟動容器, runc 啟動完容器后本身會直接退出,containerd-shim 則會成為容器進程的父進程, 負責收集容器進程的狀態, 上報給 containerd, 并在容器中 pid 為 1 的進程退出后接管容器中的子進程進行清理, 確保不會出現僵尸進程 。也就是說調用順序為:

    Dockerd -> containerd -> containerd-shim -> runc
    

    啟動一個容器ubuntu,并在容器中再運行一個bash。

    null@ubuntu:~$ sudo docker run --rm -it ubuntu /bin/bashroot@ef57fff95b80:/# bashroot@ef57fff95b80:/# ps   PID TTY          TIME CMD     1 pts/0    00:00:00 bash     9 pts/0    00:00:00 bash    12 pts/0    00:00:00 ps
    

    查看調用棧,containerd-shim(28051-28129)并沒有被施加seccomp,而容器內的兩個bash(1 -> 28075;9->28126)被施加了seccomp策略。

    root@ubuntu:/home/null# pstree -p | grep containerd-shim           |-containerd-shim(28051)-+-bash(28075)---bash(28126)           |                        |-{containerd-shim}(28052)           |                        |-{containerd-shim}(28053)           |                        |-{containerd-shim}(28054)           |                        |-{containerd-shim}(28055)           |                        |-{containerd-shim}(28056)           |                        |-{containerd-shim}(28057)           |                        |-{containerd-shim}(28058)           |                        |-{containerd-shim}(28059)           |                        |-{containerd-shim}(28060)           |                        `-{containerd-shim}(28129)root@ubuntu:/home/null# grep -i seccomp /proc/28051/statusSeccomp:        0root@ubuntu:/home/null# grep -i seccomp /proc/28075/statusSeccomp:        2root@ubuntu:/home/null# grep -i seccomp /proc/28126/statusSeccomp:        2root@ubuntu:/home/null# grep -i seccomp /proc/28052/statusSeccomp:        0......root@ubuntu:/home/null# grep -i seccomp /proc/28129/statusSeccomp:        0
    

    也就是說對容器施加seccomp 是在container-shim啟動之后,在調用runc的時候出現了問題,是否我們的seccomp策略也要將runc所必須的系統調用考慮進去呢?Zaz是否考慮了容器啟動時候的runc所必須的系統調用?

    這就需要捕獲容器在啟動時,runc所必要的系統調用了。

    Sysdig

    為了獲取容器運行時runc用了哪些系統調用,可以有很多方法,比如ftrace、strace、fanotify等,這里使用sysdig來監控容器的運行,sisdig時一款原生支持容器的系統可見性工具,項目地址:https://github.com/draios/sysdig。具體的安裝和使用方法可以參考GitHub上給出的詳細教程,這里只做簡單介紹。

    安裝完成后,直接在命令行運行sysdig,不加任何參數, sysdig 會捕獲所有的事件并將其寫入標準輸出 :

    $ sysdig285304 01:21:51.270700399 7 sshd (50485) > select285306 01:21:51.270701716 7 sshd (50485) < select res=2285307 01:21:51.270701982 7 sshd (50485) > rt_sigprocmask285308 01:21:51.270702258 7 sshd (50485) < rt_sigprocmask285309 01:21:51.270702473 7 sshd (50485) > rt_sigprocmask285310 01:21:51.270702660 7 sshd (50485) < rt_sigprocmask285312 01:21:51.270702983 7 sshd (50485) > read fd=13(/dev/ptmx) size=16384285313 01:21:51.270703971 1 sysdig (59131) > switch next=59095 pgft_maj=0 pgft_min=1759 vm_size=280112 vm_rss=18048 vm_swap=0...
    

    默認情況下,sysdig 在一行中打印每個事件的信息,格式如下:

    %evt.num %evt.time %evt.cpu %proc.name (%thread.tid) %evt.dir %evt.type %evt.args

    其中:

    evt.num 是遞增的事件編號

    evt.time 是事件時間戳

    evt.cpu 是捕獲事件的 CPU 編號

    proc.name 是生成事件的進程的名稱

    thread.tid 是產生事件的TID,對應單線程進程的PID

    evt.dir 是事件方向,> 表示進入事件,< 表示退出事件

    evt.type 是事件的名稱,例如“open”或“read”

    evt.args 是事件參數的列表。在系統調用的情況下,這些往往對應于系統調用參數,但情況并非總是如此:出于簡單或性能原因,某些系統調用參數被排除在外。

    啟動一個終端A,輸入以下命令進行監控,container.name指定捕獲容器名為ping,proc.name指定進程名為runc的包,保存為runc.scap。

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

    接著在另一個終端B啟動該容器:

    $sudo docker run --rm -it --name=ping pingtestPING 8.8.8.8 (8.8.8.8): 56 data bytes64 bytes from 8.8.8.8: seq=0 ttl=127 time=44.032 ms64 bytes from 8.8.8.8: seq=1 ttl=127 time=42.069 ms64 bytes from 8.8.8.8: seq=2 ttl=127 time=42.066 ms64 bytes from 8.8.8.8: seq=3 ttl=127 time=42.073 ms64 bytes from 8.8.8.8: seq=4 ttl=127 time=42.112 ms
    --- 8.8.8.8 ping statistics ---5 packets transmitted, 5 packets received, 0% packet lossround-trip min/avg/max = 42.066/42.470/44.032 ms
    

    執行完畢后,在終端A使用ctrl+c停止捕獲,并篩選捕獲的內容,只留系統調用,將結果保存到runc_syscall.txt中,這樣我們就得到了啟動容器時runc使用了哪些系統調用。

    $ sysdig  -p "%syscall.type" -r runc.scap | runc_syscall.txt$ cat -n runc_syscall.txt  ...  3437  rt_sigaction  3438  exit_group  3439  procexit
    

    可以發現篩選出的系統調用數還是有很多的,其中包含很多重復的系統調用,這里可以簡單的寫一個腳本,進行過濾,通過過濾后,一共有72個系統調用。

    $ python analyse.py runc_syscall.txtFilter syscall num: 72filter syscall:['clone', 'close', 'prctl', 'getpid', 'write', 'unshare', 'read', 'exit_group', 'procexit', 'setsid', 'setuid', 'setgid', 'sched_getaffinity', 'openat', 'mmap', 'rt_sigprocmask', 'sigaltstack', 'gettid', 'rt_sigaction', 'mprotect', 'futex', 'set_robust_list', 'munmap', 'nanosleep', 'readlinkat', 'fcntl', 'epoll_create1', 'pipe', 'epoll_ctl', 'fstat', 'pread', 'getdents64', 'capget', 'epoll_pwait', 'newfstatat', 'statfs', 'getppid', 'keyctl', 'socket', 'bind', 'sendto', 'getsockname', 'recvfrom', 'mount', 'fchmodat', 'mkdirat', 'symlinkat', 'umask', 'mknodat', 'fchownat', 'unlinkat', 'chdir', 'fchdir', 'pivot_root', 'umount', 'dup', 'sethostname', 'fstatfs', 'seccomp', 'brk', 'fchown', 'setgroups', 'capset', 'execve', 'signaldeliver', 'access', 'arch_prctl', 'getuid', 'getgid', 'geteuid', 'getcwd', 'getegid']
    

    將zaz生成的系統調用與我們捕獲的系統調用合二為一,系統調用數到了85個。如下:

    {    "defaultAction": "SCMP_ACT_ERRNO",    "architectures": [        "SCMP_ARCH_X86_64",        "SCMP_ARCH_X86",        "SCMP_ARCH_X32"    ],    "syscalls": [        {            "names": [                "clone",                "close",                "prctl",                "getpid",                "write",                "unshare",                "read",                "exit_group",                "procexit",                "setsid",                "setuid",                "setgid",                "sched_getaffinity",                "openat",                "mmap",                "rt_sigprocmask",                "sigaltstack",                "gettid",                "rt_sigaction",                "mprotect",                "futex",                "set_robust_list",                "munmap",                "nanosleep",                "readlinkat",                "fcntl",                "epoll_create1",                "pipe",                "epoll_ctl",                "fstat",                "pread",                "getdents64",                "capget",                "epoll_pwait",                "newfstatat",                "statfs",                "getppid",                "keyctl",                "socket",                "bind",                "sendto",                "getsockname",                "recvfrom",                "mount",                "fchmodat",                "mkdirat",                "symlinkat",                "umask",                "mknodat",                "fchownat",                "unlinkat",                "chdir",                "fchdir",                "pivot_root",                "umount",                "dup",                "sethostname",                "fstatfs",                "seccomp",                "brk",                "fchown",                "setgroups",                "capset",                "signaldeliver",                "access",                "getuid",                "getgid",                "geteuid",                "getcwd",                "getegid",                "arch_prctl",                "clock_gettime",                "connect",                "dup2",                "execve",                "exit",                "ioctl",                "open",                "poll",                "rt_sigreturn",                "set_tid_address",                "setitimer",                "setsockopt",                "socket",                "writev"            ],            "action": "SCMP_ACT_ALLOW"        }    ]}
    

    通過該文件再次運行容器,發現可以成功運行!

    null@ubuntu:~/seccomp/docker/zaz/cmd$ sudo docker run -it --rm --security-opt seccomp=seccomp_ping.json pingtestPING 8.8.8.8 (8.8.8.8): 56 data bytes64 bytes from 8.8.8.8: seq=0 ttl=127 time=43.424 ms64 bytes from 8.8.8.8: seq=1 ttl=127 time=42.873 ms64 bytes from 8.8.8.8: seq=2 ttl=127 time=42.336 ms64 bytes from 8.8.8.8: seq=3 ttl=127 time=48.164 ms64 bytes from 8.8.8.8: seq=4 ttl=127 time=42.260 ms
    --- 8.8.8.8 ping statistics ---5 packets transmitted, 5 packets received, 0% packet lossround-trip min/avg/max = 42.260/43.811/48.164 ms
    

    嘗試運行其他命令,有些命令由于缺乏必須的系統調用,會出現Operation not permitted的報錯。

    $ sudo docker run -it --rm --security-opt seccomp=seccomp_ping.json pingtest lsls: .: Operation not permitted$ sudo docker run -it --rm --security-opt seccomp=seccomp_ping.json pingtest mkdir testmkdir: can't create directory 'test': Operation not permitted
    
    容器技術系統調用
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    “內網滲透的本質是信息收集”,這句話不僅適用于傳統內網,也同樣適用于云原生環境。在進入傳統內網的后滲透階段時,首先要做的工作便是對當前所處環境做詳細的信息收集,為下一步行動做鋪墊。如果收集到主機的系統版本和補丁信息,攻擊者可以通過對比分析出適用于當前環境的系統漏洞,有的放矢地攻擊,高效率的同時也盡可能減少了痕跡。 進入云原生時代后,后滲透增加了容器逃逸的階段。在《容器逃逸技術概覽》[1]中我們了
    容器安全是一個龐大且牽涉極廣的話題,而容器的安全隔離往往是一套縱深防御的體系,牽扯到AppArmor、Namespace、Capabilities、Cgroup、Seccomp等多項內核技術和特性,但安全卻是一處薄弱則全盤皆輸的局面,一個新的內核特性可能就會讓看似無懈可擊的防線存在突破口。隨著云原生技術的快速發展,越來越多的容器運行時組件在新版本中會默認配置AppArmor策略,原本我們在《紅藍對
    7月9日,騰訊安全正式發布騰訊云容器安全服務產品TCSS,騰訊云容器安全服務為企業提供容器資產管理、鏡像安全、運行時入侵檢測等安全服務,保障容器從鏡像生成、存儲到運行時的全生命周期,幫助企業構建容器安全防護體系。Tripwire 2019年對311位IT安全專業人員進行了調研,發現60%的組織都遭遇過容器安全事故,《報告》數據也顯示63%的用戶認為容器安全是緊迫需求。目前,TCSS資產管理模塊已支持9種資產信息統計。
    目前Linux內核代碼已經達到了2700萬行量級[2],僅每年通報的Linux內核漏洞就多達數十個。Linux內核主要使用C語言編寫,由于C語言不是類型安全語言,而且偏底層,所以各種內存破壞類漏洞層出不窮。攻擊者利用內核漏洞可以達到本地提權的目的。容器技術本身依賴于Linux內核提供的Namespaces和Cgroups機制,利用內核漏洞,攻擊者可以繞過Namespaces對資源的隔離,達到逃逸的
    與云原生技術紅利相伴而來的是安全威脅和風險。近年來,在云原生蓬勃發展的產業背景下,新的安全漏洞、安全事件同樣層出不窮。隨著越來越多重要業務云原生化,安全已經成為業務落地的必要保障條件。有哪些因素會讓云原生環境變得不安全?怎樣保障云原生安全?《云原生安全: 攻防實踐與體系構建》希望能夠給出一個答案。本文為《云原生安全: 攻防實踐與體系構建》一書的精華解讀,重點介紹云原生環境下的攻防對抗。本書已在京東
    從云原生計算環境等主要領域深入分析了安全風險的來源,介紹了典型開源安全工具,提出業內首個云原生應用保護平臺模型,并分層對模型中的安全能力進行了詳細介紹
    TEE 是一個隔離的處理環境,代碼和數據在執行期間受到保護,其內存區域與處理器的其他部分分離,并提供機密性和完整性屬性。其目標是確保一個任務按照預期執行,保證初始狀態和運行時的機密性和完整性。TrustZone 技術的優勢在于它可以保護數據的安全與完整,避免數據受到惡意攻擊。在使用 TrustZone 的平臺上,通常由安全世界的受信任的特權內核來維持此類應用程序的生命。KNOX 是一款旨在為企業數據保護提供強有力保障的國防級移動安全平臺。
    近年來,網絡空間已快速上升到大國間博弈的新戰場,以政企、科研機構為代表的APT攻擊、DDoS、工業互聯網攻擊等日益嚴峻,導致政企機構重要情報數據被竊取、工業互聯系統被破壞、金融基礎設施遭受到重大經濟損失,嚴重危害到了我國國家安全和社會經濟利益。
    本文將簡單介紹eBPF技術,并分析Falco是如何使用eBPF采集系統調用,為安全監控提供數據源。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类