Episode ini membedah arsitektur seccomp: mode SECCOMP_MODE_STRICT dan SECCOMP_MODE_FILTER berbasis BPF, peran prctl dan syscall seccomp, komponen libseccomp dan seccomp_export_bpf, serta bagaimana seccomp terintegrasi dengan runc, crun, systemd, dan OpenSSH.

Di episode 1 kalian sudah memahami sejarah seccomp dan masalah yang ia pecahkan. Sekarang waktunya membuka kap mesin: bagaimana seccomp bekerja di dalam sistem? Episode ini membedah arsitekturnya — dari alur sebuah syscall, dua mode operasi (SECCOMP_MODE_STRICT dan SECCOMP_MODE_FILTER), pembagian peran antara kernel dan libseccomp, hingga bagaimana runtime container dan systemd memanfaatkannya.
Jika episode 1 menjawab kenapa, episode 2 ini menjawab bagaimana. Pahami baik-baik arsitekturnya, karena seluruh episode berikutnya — return actions, API libseccomp, hingga arg filter — hanyalah pengembangan dari kerangka yang kita bangun di sini.
Sebelum membahas seccomp, mari luruskan dulu apa yang terjadi ketika sebuah program memanggil syscall. Perhatikan alur berikut:
Aplikasi (user mode)
│ memanggil syscall, misalnya execve
▼
glibc / libc (menerjemahkan ke instruksi syscall)
▼
Kernel: menerima syscall
▼
Filter seccomp terpasang? ── tidak ──► jalankan syscall normal
│ ya
▼
Kernel mengevaluasi filter BPF
│
├── ALLOW ──► jalankan syscall
└── DENY ──► kernel menolak sesuai return actionPerhatikan poin kuncinya: filter dievaluasi di kernel, tepat setelah syscall diterima dan sebelum handler syscall dieksekusi. Ini berarti tidak ada jalan bagi aplikasi untuk menghindarinya — bahkan jika aplikasi sudah sepenuhnya dikompromikan oleh attacker, keputusan tetap ada di tangan kernel.
Seccomp memiliki dua mode operasi, dan keduanya merupakan jawaban dari dua era sejarah yang kita bahas di episode 1.
Mode tertua (kernel 2.6.12). Begitu diaktifkan, proses hanya boleh memanggil read, write, _exit, dan sigreturn. Pelanggaran apa pun langsung membunuh proses. Diaktifkan lewat prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT) dan bersifat tidak dapat dibatalkan.
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/prctl.h>
#include <linux/seccomp.h>
int main(void) {
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT) == -1) {
perror("prctl");
syscall(SYS_exit, 1);
}
write(STDOUT_FILENO, "strict mode aktif\n", 18);
syscall(SYS_exit, 0);
}Note
Perhatikan bahwa program di atas tidak memakai return 0 di akhir main. Di Linux, return dari main memicu syscall exit_group — yang TIDAK diizinkan strict mode — sehingga proses akan di-kill. Inilah alasan kenapa kita memanggil syscall(SYS_exit, 0) secara langsung, dan sekaligus bukti betapa tidak praktisnya strict mode untuk aplikasi nyata.
Mode modern (kernel 3.5). Proses memasang satu atau lebih filter BPF melalui prctl atau syscall seccomp(). Setiap filter adalah program BPF yang menerima seccomp data — berisi nomor syscall, arsitektur, instruction pointer, dan enam argumen — lalu mengembalikan sebuah return action (izinkan, tolak, bunuh, dan lain-lain).
Berbeda dengan strict mode, filter seccomp-bpf:
Hampir semua penggunaan seccomp di dunia nyata — Docker, Kubernetes, systemd, Chromium — memakai mode ini.
Seccomp terdiri dari dua lapisan yang bekerja sama: kernel sebagai penegak kebijakan, dan libseccomp sebagai pembantu pembuat kebijakan.
Di sisi kernel, filter dipasang melalui dua jalur:
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) — cara tradisional.seccomp(2) — cara modern, mendukung flag seperti SECCOMP_FILTER_FLAG_TSYNC untuk menyinkronkan filter ke seluruh thread.Setiap kali syscall dipanggil, kernel mengumpulkan data berikut ke dalam struktur seccomp_data dan memberikannya ke program BPF:
| Field | Isi |
|---|---|
nr | Nomor syscall (misalnya 59 untuk execve di x86_64) |
arch | Identitas arsitektur (misalnya SCMP_ARCH_X86_64) |
instruction_pointer | Alamat instruksi pemanggil syscall |
args[0] sampai args[5] | Enam argumen syscall |
Program BPF membaca field-field ini dan mengembalikan return action. Keindahannya: BPF seccomp dijamin berhenti — kernel menolak filter yang berpotensi loop selamanya, jadi overheadnya bisa diprediksi.
Menulis BPF dengan tangan itu mungkin, tapi menyakitkan. Bayangkan harus menulis bytecode untuk aturan sederhana seperti "tolak execve tapi izinkan sisanya" — apalagi harus menangani perbedaan nomor syscall antar arsitektur. Di sinilah libseccomp masuk.
libseccomp adalah library C yang mengabstraksi seluruh kerumitan tersebut. Kalian mendeskripsikan kebijakan secara deklaratif:
SCMP_ACT_ALLOW untuk deny-list, SCMP_ACT_ERRNO untuk allow-list).seccomp_rule_add.Dan karena kebijakan dihasilkan sebagai program BPF, ia bisa diekspor ke file dan dibedah — inilah peran seccomp_export_bpf:
# libseccomp juga dilengkapi alat bantu untuk membedah BPF
seccomp-tools dump ./program-dengan-filter
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x00 0xc000003e if (A != ARCH_X86_64) goto 0003
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x15 0x00 0x01 0x0000003b if (A == execve) goto 0005
0004: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0005: 0x06 0x00 0x00 0x00050000 return ERRNO(1)Runtime OCI seperti runc dan crun membaca profile seccomp dari config.json setiap kali container dijalankan. Profile tersebut adalah deskripsi JSON yang sangat mirip dengan apa yang kita buat lewat libseccomp:
{
"defaultAction": "SCMP_ACT_ERRNO",
"defaultErrnoRet": 1,
"architectures": ["SCMP_ARCH_X86_64", "SCMP_ARCH_X86"],
"syscalls": [
{
"names": ["read", "write", "openat", "close", "mmap", "exit_group"],
"action": "SCMP_ACT_ALLOW"
},
{
"names": ["ptrace", "reboot", "kexec_load", "swapon"],
"action": "SCMP_ACT_ERRNO"
}
]
}Inilah profile default yang dihasilkan Docker dan dipasang di setiap container — kalian bisa melihat versi lengkapnya di mesin kalian dengan docker info lalu mencari bagian SecurityOptions: seccomp.
Untuk daemon yang dijalankan sebagai service unit, systemd menyediakan direktif seccomp tanpa perlu menulis JSON. Cukup pasang SystemCallFilter di unit file:
[Service]
ExecStart=/usr/sbin/sshd -D
NoNewPrivileges=yes
SystemCallFilter=@system-service
SystemCallFilter=~@obsolete @mount @privileged
SystemCallErrorNumber=EPERMSystemCallFilter menerima allow-list (@system-service) dan deny-list (prefix ~). systemd menerjemahkan direktif ini menjadi filter seccomp yang sama dengan yang dibangun libseccomp — dengan SystemCallErrorNumber=EPERM sebagai default action.
OpenSSH menggunakan seccomp untuk privilege separation: proses preauth yang menghadapi permintaan koneksi jaringan (dan karenanya paling berisiko) dijalankan dengan filter seccomp ketat agar syscall berbahaya seperti execve, connect, dan openat diblokir. Begitu autentikasi berhasil, proses baru dengan hak pengguna dibangun dengan cara yang lebih terkontrol. Inilah pola yang sama yang akan kalian temui berulang kali: bagian program yang paling rentan berjalan di bawah filter seccomp yang paling ketat, sementara bagian yang bisa dipercaya berjalan normal.
Di episode 2 ini kalian sudah memahami kerangka arsitektur seccomp:
SECCOMP_MODE_STRICT (4 syscall, kaku) dan SECCOMP_MODE_FILTER (BPF, fleksibel).prctl / syscall seccomp(2); libseccomp menyusun program BPF-nya.seccomp_data menyediakan nomor syscall, arsitektur, dan enam argumen untuk dievaluasi BPF.runc/crun memuat profile JSON dari config.json, systemd lewat SystemCallFilter, dan OpenSSH lewat privilege separation.Di episode 3 berikutnya kita akan membedah jantung pengambilan keputusan seccomp: modes & return actions — perbandingan mendalam strict vs filter, plus panduan kapan memakai SECCOMP_RET_KILL_PROCESS, TRAP, ERRNO, TRACE, ALLOW, LOG, dan NOTIFY. Di sana kalian akan belajar bagaimana sebuah filter "menjawab" setiap syscall. Sampai jumpa!