Membongkar filter BPF dengan seccomp-tools dump dan asm, mengamati syscall asli dengan strace, serta memakai SECCOMP_RET_LOG dan auditd untuk mencatat pelanggaran. Termasuk strategi test suite deny behavior dan fallback yang aman bila filter keliru.

Di episode 6 kalian sudah melihat filter yang kompleks — user notification, argfilter, dan rantai action yang berlapis. Semakin kompleks filter, semakin besar peluang bug: aturan yang salah tulis, arsitektur yang keliru, atau syscall yang ternyata masih dibutuhkan aplikasi. Masalahnya, filter seccomp adalah kode yang tidak terlihat — ia berjalan di kernel, dan bila salah, aplikasi hanya mati tanpa penjelasan. Episode 7 ini memberi kalian tiga senjata debugging: membongkar filter BPF, mengamati syscall yang benar-benar dipanggil aplikasi, dan menguji perilaku deny secara otomatis. Prinsipnya tegas: filter adalah kode, dan kode punya bug — jangan pernah percaya sampai kalian buktikan.
seccomp-tools adalah toolkit dari ekosistem CTF yang sangat berguna untuk pekerjaan sehari-hari. Ia memakai ptrace untuk mencegat momen filter dimuat ke kernel, lalu menampilkan program BPF dalam bentuk yang bisa dibaca manusia. Install dulu:
gem install seccomp-toolsPerintah pertama yang wajib kalian hafal adalah dump — ia menjalankan binary dan menangkap filter yang di-load:
seccomp-tools dump ./appKeluarannya berupa disassembly BPF (disederhanakan):
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x00 0xc000003e if (A != 0xc000003e) goto 0003
0002: 0x20 0x00 0x00 0x00000000 A = syscall_nr
0003: 0x15 0x00 0x00 0x00000000 if (A != read) goto 0004
0004: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0005: 0x06 0x00 0x00 0x00000001 return ERRNO(1)Bacalah dari atas ke bawah: verifikasi arsitektur, baca nomor syscall, bandingkan, lalu ambil action. Cara baca seperti inilah yang membedakan orang yang paham filter dari yang hanya menyalin contoh.
Selain membaca, kalian juga bisa menulis dan mengkompilasi filter langsung dengan DSL sederhana asm. Buat file aturan seperti ini:
line: 0
line: 1
return ALLOW
return ERRNO(1)Artinya: jika nomor syscall sama dengan 0 (read) atau 1 (write), kembalikan ALLOW; selain itu ERRNO(1). Kompilasi DSL ke bytecode BPF, lalu balikkan bytecode ke assembly untuk memverifikasi:
seccomp-tools asm rule.asm
seccomp-tools disasm 20 00 00 00 04 00 00 00 15 00 00 00 00 00 00 00Tip
Pasangan asm dan disasm sangat berguna saat kalian membandingkan filter yang dimuat aplikasi dengan filter yang kalian kira kalian tulis. Kalau keduanya berbeda, selisih itulah sumber bug.
Filter yang benar harus selaras dengan syscall yang benar-benar dipanggil aplikasi. strace mencatat setiap syscall lewat ptrace — misalnya strace -f -o /tmp/app.trace ./app untuk menyimpan jejak lengkap — tapi untuk menyusun filter, mode ringkasan justru lebih berguna:
strace -f -c ./appContoh hasil (dipotong):
calls errors syscall
--------- ------ ----------------
100 60 write
100 0 openat
50 0 mmap
30 0 read
--------- ------ ----------------
310 60 totalKolom errors adalah alarm tersembunyi: syscall yang gagal bukan berarti diblokir seccomp, tapi sering kali menandakan hal yang perlu diselidiki. Ringkasan ini menjadi baseline yang tepat untuk menyusun allowlist di episode 8.
Note
strace dan seccomp-tools sama-sama memakai ptrace. Jalankan keduanya dari lingkungan tanpa Yama yang menghalangi (kernel.yama.ptrace_scope rendah), dan ingat: filter seccomp tetap berlaku untuk proses yang di-strace, jadi observasi aplikasi yang sudah ter-filter akan menampilkan EPERM sebagai bentuk pengamatan yang valid.
Membangun filter yang benar butuh umpan balik, dan umpan balik terbaik adalah log. Sejak kernel 4.14, libseccomp menyediakan action SCMP_ACT_LOG: syscall tetap dijalankan, tapi setiap kemunculannya dicatat oleh subsistem audit. Ini sempurna untuk uji coba — aplikasi tidak diblokir, dan kalian melihat syscall apa saja yang muncul.
Setelah auditd aktif, semua catatan ini masuk ke /var/log/audit/audit.log dengan tipe 1326. Cara membacanya:
sudo ausearch -m SECCOMP -ts recentContoh satu baris audit:
type=1326 audit(1710000000.123:789): auid=1000 uid=1000 gid=1000
subj=unconfined pid=4567 comm="app" exe="/usr/bin/app"
arch=c000003e syscall=59 code=0x7ffc0000Baca yang penting saja:
code=0x7ffc0000 — itu SECCOMP_RET_LOG. Jika kodenya berbeda, action-nya juga berbeda (0x00050000 untuk ERRNO, 0x7fff0000 untuk ALLOW).syscall=59 — nomor syscall pada arsitektur itu, belum tentu namanya; petakan dengan ausyscall 59 (59 adalah execve di x86_64), dan cocokkan comm/exe untuk tahu proses mana yang memanggil.Tip
Bila kalian tidak ingin memasang auditd, filter SCMP_ACT_LOG juga ikut tercatat di kernel log — cek dengan journalctl -k dan grep seccomp. Auditd hanya memberi format yang lebih mudah dicari.
Inilah alur paling aman untuk membangun filter di aplikasi nyata:
strace -f -c di lingkungan yang mewakili produksi, lalu susun allowlist dengan default action SCMP_ACT_ERRNO.SCMP_ACT_LOG sebagai default sementara di staging — aplikasi jalan normal, semua syscall tercatat.ERRNO, jalankan lagi, pastikan tidak ada syscall yang hilang.Yang terpenting: sediakan jalur keluar. Filter yang salah di produksi membuat aplikasi crash seketika — simpan tombol untuk menjalankan aplikasi tanpa filter (variabel env atau flag --no-seccomp) sebagai recovery darurat, jangan pernah melewati staging, dan mulai dengan LOG di awal, bukan langsung ERRNO.
Warning
Jalur keluar tanpa filter hanyalah alat recovery, bukan konfigurasi produksi. Jika kalian menyalakan fallback untuk waktu lama, artinya filter kalian salah — perbaiki, jangan dipertahankan. Kontainer yang menonaktifkan seccomp demi kenyamanan adalah rumah tanpa pintu terkunci.
Umpan balik log itu observasional; yang lebih kuat lagi adalah menguji secara deterministik. Prinsipnya: program kecil yang menjalankan filter yang sama dengan aplikasi produksi, lalu memanggil syscall yang diizinkan dan yang diblokir, dan membandingkan errno-nya. Contoh harness dalam C:
#include <seccomp.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
static int pass = 0, fail = 0;
static void expect_errno(int got, int want) {
if (got == want) {
pass++;
} else {
fail++;
printf("errno = %d, diharapkan %d\n", got, want);
}
}
int main(void) {
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_ALLOW);
seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(chmod), 0);
seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(mount), 0);
seccomp_load(ctx);
errno = 0; chmod("/tmp/x", 0600); expect_errno(errno, EPERM);
errno = 0; mount(0, 0, 0, 0, 0); expect_errno(errno, EPERM);
errno = 0; getpid(); expect_errno(errno, 0);
printf("pass=%d fail=%d\n", pass, fail);
return fail ? 1 : 0;
}Perhatikan strukturnya: selain menguji syscall yang harus diblokir, harness juga menguji syscall yang harus berjalan (di sini getpid) — filter yang memblokir terlalu banyak sama bahayanya dengan yang memblokir terlalu sedikit. Kompilasi dengan gcc -o test_deny test_deny.c -lseccomp lalu jalankan ./test_deny. Karena keluarannya berupa exit code (0 sukses, 1 gagal), harness ini bisa langsung masuk CI — setiap perubahan filter harus lolos, dan setiap kegagalan pengujian harus dipertanggungjawabkan, bukan diam-diam dihapus.
Di episode 7 ini kalian telah memegang tiga kunci debugging filter: membongkar BPF dengan seccomp-tools dump dan menyusunnya lewat asm/disasm, mengamati perilaku syscall nyata dengan strace -c, serta memanfaatkan SCMP_ACT_LOG dan ausearch -m SECCOMP untuk mencatat pelanggaran tanpa memblokir. Kalian juga tahu alur log-dulu-enforce-kemudian dan test suite deterministik yang membuktikan deny behavior.
Kunci yang harus dibawa pulang:
SCMP_ACT_LOG adalah mode pengintaian; pakai sebelum beralih ke ERRNO.Semua alat ini akan terasa sia-sia tanpa satu hal: tempat memakainya dalam skala besar. Di episode 8, kita masuk ke Profile Container (Docker/runc): profil docker-default, daftar syscall yang diblokir, format JSON profil OCI, dan cara membangun profil kustom untuk aplikasi kalian.