Metodologi merancang filter BPF yang optimal: evaluasi linear, aturan argumen presisi dengan SCMP_CMP64, komposisi filter berlapis, dan pola minimal syscall set per workload. Ditutup dengan pengukuran memakai strace untuk membangun allow-list yang aman dan teruji.

Di episode 16 kalian memahami antarmuka kernel: filter bersifat aditif, diwariskan ke anak proses, dan tidak bisa dicabut. Batasan itu memberi satu pesan yang jelas: sebuah filter harus benar sejak pertama kali dipasang, karena memperbaikinya berarti mengganti proses yang memakainya. Desain filter, dengan kata lain, bukan pekerjaan mengetik aturan — ia adalah pekerjaan rekayasa.
Inilah episode puncak dari seluruh pembahasan teknis series ini: advanced filter design. Kalian sudah tahu gerbangnya (seccomp), bahannya (BPF), batasannya (kernel), dan cara mengamatinya (audit). Sekarang saatnya menyusun semuanya menjadi metodologi: bagaimana menulis filter yang presisi tanpa membuatnya terlalu besar, bagaimana mengukur syscall yang benar-benar dibutuhkan workload, dan bagaimana merangkai filter menjadi lapisan-lapisan yang saling menguatkan.
Pola pikir yang akan kita pakai sepanjang episode ini: filter yang baik bukan filter yang paling banyak aturannya, melainkan filter yang paling sesuai dengan workload — sesedikit mungkin, setepat mungkin. Mari mulai.
Fondasi pertama desain filter: program BPF dieksekusi secara linear setiap kali sebuah syscall dipanggil. Kernel berjalan dari instruksi pertama, menelusuri aturan satu per satu, sampai mencapai sebuah return action. Setiap aturan yang kalian tambahkan berarti satu langkah ekstra di jalur panas kernel — untuk setiap syscall, setiap proses, setiap waktu.
Bayangkan antrean pemeriksaan di bandara. Satu petugas dengan daftar singkat memproses penumpang dalam hitungan detik; dua puluh petugas dengan daftar seribu nama membuat antrean mengular. Inilah mengapa episode 18 akan membahas biaya seccomp dengan serius — tetapi sejak sekarang kalian harus memegang prinsipnya: filter yang ramping lebih cepat, lebih mudah di-review, dan lebih aman (mengingat CVE filter oversized di episode 14).
Tidak semua syscall harus diblokir atau diizinkan secara membabi buta. Untuk syscall yang berbahaya hanya dalam kondisi tertentu, kalian bisa menambahkan pemeriksaan argumen. libseccomp menyediakan keluarga SCMP_CMP untuk ini — dan SCMP_CMP64 untuk membandingkan argumen 64-bit penuh.
Contoh klasik: kill adalah syscall yang sah, tetapi aplikasi tidak perlu membunuh proses lain sesuka hati. Batasi agar kill hanya diizinkan menarget PID dirinya sendiri:
#include <seccomp.h>
#include <unistd.h>
#include <stdint.h>
scmp_filter_ctx ctx;
ctx = seccomp_init(SCMP_ACT_ERRNO);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(kill),
1,
SCMP_CMP64(0, SCMP_CMP_EQ, (int64_t) getpid()));
seccomp_load(ctx);Bedah baris SCMP_CMP64(0, SCMP_CMP_EQ, (int64_t) getpid()): angka 0 menunjuk argumen pertama kill (yaitu PID target), SCMP_CMP_EQ berarti perbandingan sama dengan, dan nilai yang dibandingkan adalah PID proses itu sendiri. Hasilnya: kill ke proses lain ditolak, kill ke diri sendiri diizinkan.
Warning
Pemeriksaan argumen adalah sumber overhead dan kompleksitas terbesar — dan lokasi bug CVE GHSA-4q85-33p6-j5g6 di episode 14. Gunakan hanya untuk syscall yang benar-benar berbahaya secara kondisional. Sebelum menerapkan, uji bahwa perbandingannya benar-benar menegakkan batasan (episode 15), dan setelah update library, uji ulang.
Kernel membatasi ukuran program BPF — dan pengalaman di episode 14 menunjukkan bahwa filter raksasa juga memicu bug di libseccomp. Beberapa pedoman untuk menjaga filter tetap kecil:
SCMP_CMP menambah instruksi dan kompleksitas merge. Tambahkan hanya saat nilainya nyata.Sifat aditif filter membuka pola komposisi yang elegan. Daripada satu profile yang mencoba melakukan segalanya, pecah menjadi lapisan yang masing-masing punya tanggung jawab:
Lapisan dasar — berlaku untuk semua workload: blokir syscall berbahaya secara universal (mount, ptrace, kexec_load, dan teman-teman dari episode 13).
Lapisan per-service — khusus untuk satu aplikasi: allow-list syscall yang benar-benar dipakai workload tersebut.
{
"defaultAction": "SCMP_ACT_ALLOW",
"architectures": ["SCMP_ARCH_X86_64"],
"syscalls": [
{
"names": ["mount", "ptrace", "kexec_load", "userfaultfd"],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 1
}
]
}Di level proses (aplikasi C, systemd), kedua lapisan dipasang sebagai dua filter terpisah — deny-list di dasar, allow-list di atasnya. Karena filter bersifat aditif, hasil akhirnya adalah irisan keduanya: syscall di allow-list dan bukan di deny-list. Di level container, Docker hanya mendukung satu profile per container — di sana kalian cukup menggabungkan kedua daftar menjadi satu JSON.
Sebuah filter tidak harus bersikap sama terhadap semua syscall — ia adalah pohon keputusan bercabang. libseccomp menyusun aturan kalian menjadi percabangan: kernel menguji arsitektur, memeriksa nomor syscall, lalu (jika perlu) argumen, dan menuju return action yang sesuai. Struktur percabangan ini dihasilkan otomatis dari urutan dan jenis aturan, tetapi hasilnya bisa kalian kendalikan dengan tiga keputusan desain:
SCMP_ACT_ALLOW untuk deny-list (nyaris semua syscall jatuh ke cabang izin) atau SCMP_ACT_ERRNO untuk allow-list (semua syscall lain jatuh ke cabang tolak).read bisa di-ALLOW, mount di-ERRNO, dan io_uring_setup di-NOTIFY — masing-masing dengan cabangnya sendiri.Poin ketiga layak digarisbawahi. NOTIFY tidak harus digunakan secara global. Kalian bisa membiarkan deny-list jalan seperti biasa dan hanya menyisihkan satu-dua syscall yang memang butuh keputusan dinamis ke mode NOTIFY — misalnya io_uring_setup atau bpf — sementara syscall lain tetap dievaluasi in-kernel. Hasilnya: presisi supervisor hanya di titik yang benar-benar membutuhkannya, dan syscall jalur panas tidak terseret ke biaya NOTIFY yang kalian pelajari di episode 6. Prinsip komposisi ini akan kembali dibahas dari sisi biaya di episode 18.
Sekarang pola yang paling praktis dan paling sering diabaikan: ukur dulu, baru tulis filter. Jangan menebak syscall yang dibutuhkan aplikasi — ukur. Langkahnya:
1. Profil workload di staging. Jalankan workload dalam kondisi realistis (traffic uji, skenario produksi) sambil mencatat syscall dengan strace -c:
strace -c -f -p 12342. Baca hasilnya sebagai daftar syscall yang benar-benar dipakai.
3. Susun allow-list dari daftar itu. Hanya syscall yang muncul — ditambah cadangan kecil untuk jalur inisialisasi dan error handling yang tidak terekam.
4. Uji dengan mode LOG. Jalankan di staging dengan SCMP_ACT_LOG (episode 15), periksa syscall yang tidak sengaja terblokir, dan perbaiki sebelum beralih ke SCMP_ACT_ERRNO.
5. Ulangi saat aplikasi berubah. Setiap rilis yang mengubah perilaku I/O atau networking berpotensi mengubah profil syscall — ukur ulang.
Kapan harus blokir syscall, kapan membatasi argumen, dan kapan membiarkan lewat? Pedoman sederhananya:
mount, ptrace, kexec_load. Ini keputusan yang murah dan jelas.kill di atas. Gunakan SCMP_CMP secukupnya.read, write, futex. Menambah cek argumen di jalur panas membayar biaya yang tidak perlu.Tip
Aturan praktis yang mudah diingat: deny-list untuk hal yang jelas-jelas berbahaya, allow-list untuk hal yang benar-benar dipakai, dan argumen hanya untuk titik-titik yang benar-benar rawan. Filter yang mengikuti pola ini ramping, mudah di-review, dan bertahan lama.
1. Menebak tanpa mengukur. Menulis filter dari perasaan menghasilkan dua kesalahan sekaligus: syscall penting terblokir dan aplikasi rusak, atau syscall berbahaya lolos karena tidak terpikir. Ukur selalu.
2. Deny-list sebagai satu-satunya policy. Deny-list tidak pernah lengkap — ia hanya memblokir yang kalian ketahui. Tambahkan allow-list sebagai lapisan utama.
3. Over-engineering aturan argumen. Setiap SCMP_CMP menambah kompleksitas, overhead, dan risiko bug merge. Jika pembatasan argumen tidak mencegah skenario yang realistis, jangan dipasang.
4. Filter raksasa. Melanggar batas ukuran BPF, memicu CVE library, dan sulit di-review. Pecah menjadi lapisan, prioritaskan allow-list.
5. Lupa mengukur ulang setelah update aplikasi. Profil syscall berubah seiring versi aplikasi. Filter yang dirancang untuk v1 bisa memblokir kebutuhan v2.
Pada episode 17 ini kalian telah merangkai seluruh materi series menjadi metodologi desain filter: memahami biaya evaluasi linear BPF, menyusun aturan argumen presisi dengan SCMP_CMP64, menghindari filter oversized, merangkai komposisi filter berlapis (dasar dan per-service), menerapkan pola minimal syscall set yang diukur dengan strace dan bpftrace, serta menyusun percabangan yang memadukan return action berbeda — termasuk menyisipkan NOTIFY hanya di titik yang membutuhkan.
Poin kunci yang perlu kalian bawa:
SCMP_CMP64) memberi presisi, tapi pakai secukupnya dan uji terus.Desain yang baik menghasilkan filter yang benar sejak awal — dan karena filter tidak bisa dicabut, itu adalah keharusan, bukan pilihan. Pertanyaan yang tersisa sekarang adalah soal harga: berapa biaya nyata dari semua presisi ini? Di episode 18 berikutnya kita akan membahas performance & overhead — mengukur biaya filter per syscall, optimasi kernel seperti BPF JIT dan filter caching, serta menjaga keseimbangan keamanan dan performa di skala ribuan container. Sampai jumpa!