Skip to content

Seccomp syscall filtering

Linux seccomp (SECCOMP_MODE_STRICT / SECCOMP_MODE_FILTER) installs a classic-BPF program — via prctl(PR_SET_SECCOMP, ...) or seccomp(2) — that inspects each syscall's number and arguments and returns an action (kill, errno, trap, allow), shrinking the kernel attack surface reachable from a compromised process.

Mechanism

Neden çalışır

Her privilege-escalation ya da sandbox-escape exploit'i nihayetinde kernel'e syscall interface üzerinden ulaşır. Kernel devasa bir saldırı yüzeyidir (yüzlerce syscall, her biri sub-command'lar ve driver yollarıyla); çoğu program yalnızca minik bir alt kümeye ihtiyaç duyar. Seccomp least syscall ilkesini enforce eder: syscall entry sınırında bir filtre araya koyar, böylece bir process ihtiyaç duymadığı çağrılardan gönüllü ve geri dönülmez şekilde vazgeçer.

İki mod vardır:

  • SECCOMP_MODE_STRICT — thread yalnızca read(2), write(2), _exit(2) (exit_group(2) değil) ve sigreturn(2)'yi çağırabilir; başka her şey SIGKILL alır.
  • SECCOMP_MODE_FILTER ("seccomp-bpf") — process, her syscall için read-only bir struct seccomp_data üzerinde çalışan ve bir action döndüren klasik bir BPF programı sağlar.

Güvenlik invariant'ı monotonik, ayrıcalıksız öz-kısıtlama'dır: bir kez kurulduktan sonra bir filtre "kaldırılamaz" ve çağıran thread'e ve onun gelecekteki tüm child'larına uygulanır. Ayrıcalıksız bir process yalnızca kendi privilege'larını azaltabileceği için, filtre önce hiç yeni privilege vermediğini tesis etmelidir: task prctl(PR_SET_NO_NEW_PRIVS, 1)'i çağırmalı (ya da CAP_SYS_ADMIN tutmalı), aksi halde kurulum -EACCES döndürür. İşte bu, seccomp'un sıradan, güvenilmeyen kod tarafından güvenle uygulanmasını sağlar — kernel, asla erişim vermeye kandırılamayacağını bilir.

Walkthrough

1. Strict mode (minimal sandbox):

#include <sys/prctl.h>
#include <linux/seccomp.h>
prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);   /* only read/write/_exit/sigreturn now */

2. Filtrenin gördüğü veri. BPF programı şunun üzerinde değerlendirilir:

struct seccomp_data {
    int   nr;                    /* system call number              */
    __u32 arch;                  /* AUDIT_ARCH_* (calling convention)*/
    __u64 instruction_pointer;   /* CPU IP at the syscall            */
    __u64 args[6];               /* up to 6 syscall arguments        */
};

3. Ham classic-BPF filtresi — yalnızca listelenen syscall'lara izin ver, aksi halde kill et (number/ABI karışıklığından kaçınmak için önce arch kontrolü):

#include <linux/seccomp.h>
#include <linux/filter.h>
#include <linux/audit.h>
#include <sys/prctl.h>

struct sock_filter prog[] = {
    /* load arch and reject anything but x86-64 */
    BPF_STMT(BPF_LD  | BPF_W | BPF_ABS, offsetof(struct seccomp_data, arch)),
    BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 1, 0),
    BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),

    /* load syscall number */
    BPF_STMT(BPF_LD  | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)),
    BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_read,  3, 0),
    BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_write, 2, 0),
    BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_exit,  1, 0),
    BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | (EPERM & SECCOMP_RET_DATA)),
    BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
};
struct sock_fprog bpf = { .len = sizeof(prog)/sizeof(prog[0]), .filter = prog };

prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &bpf);   /* or seccomp(SECCOMP_SET_MODE_FILTER, 0, &bpf) */

4. Filtre return action'ları (en yüksek öncelik önce):

SECCOMP_RET_KILL_PROCESS  kill whole process (core dump)
SECCOMP_RET_KILL_THREAD   kill calling thread        (alias SECCOMP_RET_KILL)
SECCOMP_RET_TRAP          deliver SIGSYS
SECCOMP_RET_ERRNO         skip syscall, return chosen errno
SECCOMP_RET_USER_NOTIF    hand off to a userspace supervisor (seccomp notify)
SECCOMP_RET_TRACE         notify an attached ptracer
SECCOMP_RET_LOG           run syscall, but log it
SECCOMP_RET_ALLOW         run syscall normally
libseccomp eşdeğeri

libseccomp aynı cBPF'yi sizin için inşa eder: scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_ERRNO(EPERM)); sonra seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0); (syscall başına tekrarla) ve seccomp_load(ctx);. Ayrıca multi-arch ve argüman karşılaştırmalarını da yönetir.

Detection

Denetim: bir SECCOMP_RET_LOG action'ı (ve SECCOMP_RET_KILL* denetim kaydı), ihlal eden syscall'ı adlandıran kernel audit/dmesg entry'leri üretir; reddedilen syscall'lar böyle gözlemlenir. /proc/<pid>/status, Seccomp: field'ını (0 disabled, 1 strict, 2 filter) ve SeccompFilters: sayısını sunar.

Mitigation

(Artık risk / bypass.) Seccomp kernel'i daraltır ama mühürlemez:

  • Filtrenin hâlâ izin verdiği syscall'lar her ne ise sömürülebilir kalır. İzin verilen bir çağrı üzerinden ulaşılabilen bir bug (örn. izin verilen bir ioctl ya da socket yolu) etkilenmez — seccomp saldırı-yüzeyi azaltmasıdır, bug eliminasyonu değil.
  • Argüman incelemesi sığdır. cBPF pointer argümanlarını dereference edemez (bir path ya da struct'ı incelemek için bir user pointer'ı takip etmek yok), bu yüzden işaret edilen veri üzerinde filtreleme imkânsızdır; bu ayrıca argüman belleği üzerinde TOCTOU oyunlarına da olanak tanır. Number/ABI karışıklığından yalnızca filtre arch kontrol ederse kaçınılır.
  • bpf(2), ptrace(2) ya da writable /proc yollarına izin veren fazla esnek bir politika, bir saldırganın yine de BPF JIT spray'e ya da diğer kernel primitive'lerine ulaşmasına izin verebilir. Seccomp namespace'ler ve LSM'lerle birlikte çalışır — onların yerini almaz.

References