Belajar Seccomp - Debugging & Testing Filters
Episode 7 of 23

Belajar Seccomp - Debugging & Testing Filters

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.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Pendahuluan

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.

Membongkar BPF: seccomp-tools

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:

Install seccomp-tools (Ruby gem)
gem install seccomp-tools

Perintah pertama yang wajib kalian hafal adalah dump — ia menjalankan binary dan menangkap filter yang di-load:

Dump BPF yang dimuat sebuah binary
seccomp-tools dump ./app

Keluarannya berupa disassembly BPF (disederhanakan):

Contoh output seccomp-tools dump
 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:

rule.asm — izinkan read dan write saja
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:

Kompilasi DSL, lalu balikkan ke assembly
seccomp-tools asm rule.asm
seccomp-tools disasm 20 00 00 00 04 00 00 00 15 00 00 00 00 00 00 00

Tip

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.

Mengamati Syscall: strace

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:

Ringkasan frekuensi syscall
strace -f -c ./app

Contoh hasil (dipotong):

Ringkasan syscall aplikasi (dipotong)
  calls  errors syscall
--------- ------ ----------------
    100     60 write
    100      0 openat
     50      0 mmap
     30      0 read
--------- ------ ----------------
    310     60 total

Kolom 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.

SECCOMP_RET_LOG dan auditd

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:

Tampilkan event seccomp terbaru
sudo ausearch -m SECCOMP -ts recent

Contoh satu baris audit:

Event seccomp di audit.log
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=0x7ffc0000

Baca 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.

Strategi Log Dulu, Enforce Kemudian

Inilah alur paling aman untuk membangun filter di aplikasi nyata:

  1. Bangun baseline — catat semua syscall dengan strace -f -c di lingkungan yang mewakili produksi, lalu susun allowlist dengan default action SCMP_ACT_ERRNO.
  2. Jalankan dengan SCMP_ACT_LOG sebagai default sementara di staging — aplikasi jalan normal, semua syscall tercatat.
  3. Bandingkan log dengan allowlist — setiap syscall yang muncul tapi tidak ada di daftar dipelajari: dibutuhkan (tambahkan) atau tidak (biarkan diblokir).
  4. Balik ke enforce — ganti default menjadi 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.

Test Suite: Membuktikan Perilaku Deny

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:

test_deny.c — verifikasi deny dan allow
#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.

Penutup

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:

  • Filter adalah kode — bongkar, amati, dan uji sebelum mempercayainya.
  • SCMP_ACT_LOG adalah mode pengintaian; pakai sebelum beralih ke ERRNO.
  • Test suite harus memeriksa deny DAN allow; exit code 1 = filter salah.

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.

Belajar Seccomp - Debugging & Testing Filters | Belajar Seccomp