Mengenal API inti libseccomp: seccomp_init, seccomp_rule_add, seccomp_load, seccomp_reset, dan seccomp_export_bpf, lalu mempraktikkan filter C pertama yang memblokir execve dengan default action allow.

Di episode 3 kalian sudah mengenal return actions dan cara berpikir tentang keputusan seccomp. Sekarang saatnya kotoran: menulis filter sungguhan dengan libseccomp. Episode ini adalah episode paling praktis sejauh ini — kalian akan menulis, meng-compile, dan menjalankan filter C pertama kalian.
Kita akan membahas lima fungsi inti API, lalu membangun satu contoh lengkap: filter yang memblokir execve dengan default action allow. Lewat contoh ini kalian akan melihat seluruh siklus hidup sebuah filter — dari inisialisasi, penambahan aturan, pemuatan ke kernel, hingga ekspor BPF-nya.
Menulis BPF dengan tangan itu mungkin, tapi mengapa harus? Untuk aturan "tolak execve di arsitektur x86_64" saja kalian harus tahu bahwa nomor syscall execve adalah 59, menulis instruksi BPF untuk membandingkannya, dan menangani perbedaan arsitektur — semua secara manual.
libseccomp menghilangkan seluruh kerumitan itu. Kalian menulis kebijakan deklaratif dalam bahasa C, dan libseccomp menghasilkan program BPF yang benar untuk arsitektur target, termasuk mengelola perbedaan nomor syscall antar arsitektur dan menambahkan prolog pengecekan arsitektur secara otomatis.
Lima fungsi berikut adalah tulang punggung hampir semua filter yang akan kalian tulis:
| Fungsi | Tugas |
|---|---|
seccomp_init(action) | Membuat filter context baru dengan default action tertentu |
seccomp_rule_add(ctx, action, syscall, argc, ...) | Menambahkan aturan: syscall mana, aksi apa, dan (opsional) arg filter |
seccomp_load(ctx) | Menyusun BPF dan memasang filter ke kernel lewat syscall seccomp(2) |
seccomp_reset(ctx, action) | Mengosongkan seluruh aturan dan mengubah default action, tanpa membuat context baru |
seccomp_export_bpf(ctx, fd) | Menulis program BPF yang sudah tersusun ke sebuah file descriptor |
Dua fungsi pendukung yang juga wajib dikenal: seccomp_release(ctx) untuk membebaskan memori, dan seccomp_syscall_resolve_name(name) untuk menerjemahkan nama syscall menjadi nomor.
Keputusan pertama saat membuat filter adalah default action — inilah yang menentukan filosofi filter kalian:
SCMP_ACT_ALLOW sebagai default → deny-list. Kalian memblokir syscall tertentu, sisanya bebas. Mudah, tapi rawan lupa memblokir syscall berbahaya baru.SCMP_ACT_ERRNO(errno) sebagai default → allow-list. Kalian mengizinkan syscall tertentu, sisanya ditolak. Lebih aman, tapi butuh daftar syscall yang lengkap.Profil container default (Docker, runc) menggunakan pendekatan allow-list dengan SCMP_ACT_ERRNO sebagai default. Untuk contoh kita di episode ini, kita pakai deny-list — lebih singkat dan fokus pada satu syscall berbahaya.
Tujuan kita: sebuah program yang memasang filter seccomp dengan default action SCMP_ACT_ALLOW, lalu memblokir execve dengan SCMP_ACT_ERRNO(EPERM). Setelah filter aktif, kita coba memanggil execve untuk melihat hasilnya.
#include <seccomp.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
int main(void) {
scmp_filter_ctx ctx;
char *argv[] = {"/bin/ls", NULL};
char *envp[] = {NULL};
/* 1. Default action: izinkan semua syscall */
ctx = seccomp_init(SCMP_ACT_ALLOW);
if (ctx == NULL) {
perror("seccomp_init");
return 1;
}
/* 2. Aturan: blokir execve dengan errno EPERM */
if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(execve), 0) != 0) {
perror("seccomp_rule_add");
return 1;
}
/* 3. Susun BPF dan pasang filter ke kernel */
if (seccomp_load(ctx) != 0) {
perror("seccomp_load");
return 1;
}
seccomp_release(ctx);
printf("filter aktif: execve diblokir\n");
/* 4. Coba jalankan program lain - harus gagal */
if (execve("/bin/ls", argv, envp) == -1) {
printf("execve ditolak: %s\n", strerror(errno));
}
return 0;
}Mari kita bedah alurnya baris per baris:
seccomp_init(SCMP_ACT_ALLOW) membuat context filter dengan kebijakan default "izinkan semuanya". Dari sini kita akan menyisakan pengecualian.
seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(execve), 0) menambahkan aturan: ketika syscall execve dipanggil, filter mengembalikan ERRNO(EPERM) — syscall dibatalkan dengan error "Operation not permitted". Argumen terakhir 0 berarti tidak ada filter argumen (kita pelajari di episode 5).
seccomp_load(ctx) menyusun program BPF dari seluruh aturan dan memasangnya ke kernel. Setelah pemanggilan ini, filter berlaku selamanya untuk proses ini — tidak bisa dibatalkan.
seccomp_release(ctx) membebaskan memori context karena tugasnya sudah selesai.
execve("/bin/ls", argv, envp) adalah pengujiannya: setelah filter aktif, panggilan ini akan gagal dan errno akan berisi EPERM.
Sekarang kompilasi dan jalankan:
gcc -o deny-execve deny_execve.c -lseccomp./deny-execve
filter aktif: execve diblokir
execve ditolak: Operation not permittedNote
Perhatikan flag -lseccomp pada perintah gcc. Ini memberitahu linker untuk menyambungkan program dengan library libseccomp. Tanpa flag ini, program tidak akan bisa menemukan fungsi seccomp_init dan kawan-kawannya.
Selamat — kalian baru saja menulis dan menjalankan filter seccomp pertama! Program kalian sekarang memiliki aturan yang ditegakkan kernel: tidak peduli seberapa parah kode yang dieksekusi setelah filter aktif, execve tidak akan pernah berhasil.
Salah satu kekuatan libseccomp adalah kemampuan mengekspor program BPF yang tersusun. Ini berguna untuk audit dan debugging. Tambahkan blok berikut di program kalian sebelum seccomp_load:
#include <fcntl.h>
/* Ekspor program BPF ke file sebelum dimuat */
int bpf_fd = open("filter.bpf", O_CREAT | O_WRONLY, 0644);
if (bpf_fd >= 0) {
seccomp_export_bpf(ctx, bpf_fd);
close(bpf_fd);
}Setelah itu, periksa file filter.bpf dengan seccomp-tools:
seccomp-tools disassemble filter.bpf
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x00 0xc000003e if (A != ARCH_X86_64) goto 0002
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0005
0004: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0005: 0x06 0x00 0x00 0x00050000 return ERRNO(1)Lihat bagaimana libseccomp menghasilkan prolog pengecekan arsitektur (baris 0000-0001) dan perbandingan nomor syscall (baris 0002-0003) secara otomatis. Inilah yang tidak perlu kalian tulis dengan tangan — dan mengapa libseccomp menjadi standar industri.
Untuk memperkuat pemahaman, bandingkan dengan versi allow-list. Ganti dua baris kunci:
/* Default: tolak semua dengan EPERM */
ctx = seccomp_init(SCMP_ACT_ERRNO(EPERM));
/* Izinkan syscall yang benar-benar dibutuhkan program ini */
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);Dengan pendekatan ini, program hanya boleh menulis ke stdout dan keluar — semuanya ditolak dengan EPERM. Lebih aman, tapi butuh kerja lebih untuk mendaftarkan semua syscall yang dibutuhkan. Inilah trade-off yang selalu kalian hadapi: deny-list itu cepat, allow-list itu aman.
Di episode 4 ini kalian sudah menulis filter seccomp pertama:
seccomp_init(action) menetapkan default action dan filosofi filter (deny-list vs allow-list).seccomp_rule_add menambahkan aturan per syscall.seccomp_load menyusun BPF dan memasang filter ke kernel secara permanen.seccomp_reset memungkinkan reuse context, seccomp_export_bpf untuk audit dan debugging.gcc -o program program.c -lseccomp dan jalankan.Yang paling penting untuk diingat: setelah seccomp_load, filter tidak bisa dibatalkan — jadi rancang kebijakan kalian dengan hati-hati dan uji di VM (ingat episode 0).
Di episode 5 berikutnya kita akan naik level: arg filter dan architecture handling — menyaring syscall berdasarkan argumennya dengan SCMP_CMP (misalnya membatasi socket hanya untuk domain AF_UNIX), menangani multi-arsitektur x86_64/i386/arm64, dan bahaya syscall multiplexing x32. Sampai jumpa!