Belajar Seccomp - ArgFilter & Architecture Handling
Episode 5 of 23

Belajar Seccomp - ArgFilter & Architecture Handling

Memahami seccomp_rule_add dengan SCMP_CMP untuk menyaring syscall berdasarkan argumennya, plus menangani multi-arsitektur x86_64, i386, dan arm64 beserta bahaya syscall multiplexing x32.

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

Pendahuluan

Di episode 4 kalian sudah menulis filter pertama yang menyaring berdasarkan nama syscall. Tapi menyaring hanya berdasarkan nama sering kali tidak cukup. Bayangkan syscall socket: ia dipakai untuk membuat socket TCP (AF_INET) yang menghubungkan aplikasi ke jaringan luar — sekaligus socket UNIX (AF_UNIX) yang hanya komunikasi antar proses di mesin yang sama. Kalian mungkin ingin mengizinkan satu tapi menolak yang lain — dan perbedaannya hanya terlihat dari argumen syscall tersebut.

Episode ini membahas dua kemampuan lanjutan libseccomp: arg filter dengan SCMP_CMP, dan architecture handling untuk sistem multi-arsitektur. Keduanya adalah kunci untuk menulis filter yang benar-benar presisi dan aman.

Apa Itu Arg Filter?

Arg filter adalah aturan yang menyaring syscall berdasarkan nilai argumennya, bukan hanya namanya. Ingat kembali seccomp_data dari episode 2: kernel menyediakan enam argumen syscall (args[0] sampai args[5]) kepada program BPF. libseccomp mengekspos ini lewat parameter SCMP_CMP pada seccomp_rule_add.

Sintaks seccomp_rule_add dengan arg filter
seccomp_rule_add(ctx, action, syscall, argc, SCMP_CMP(...), ...);

Argumen argc menyatakan berapa banyak kondisi argumen yang harus dipenuhi (bisa 0, 1, atau lebih). Jika argc lebih dari nol, setiap kondisi SCMP_CMP(...) harus bernilai benar — ibarat rangkaian AND.

Satu batasan penting yang wajib kalian pahami sejak awal: kernel seccomp hanya membandingkan nilai argumen, bukan isi memori yang ditunjuk argumen. Kalian bisa membandingkan nilai pointer-nya, tapi tidak bisa membaca string yang dituju pointer tersebut. Untuk menyaring berdasarkan isi path file (misalnya "hanya boleh membuka /etc/passwd"), kalian butuh mekanisme lain seperti Landlock — bukan seccomp murni.

Operator SCMP_CMP

SCMP_CMP mendukung enam operator perbandingan. Bentuk umumnya SCMP_CMP(index, op, ...) dengan index adalah posisi argumen (0 sampai 5):

OperatorMaknaContoh Penggunaan
SCMP_CMP_EQarg == datum_asocket hanya jika domain-nya AF_UNIX
SCMP_CMP_NEarg != datum_aclone hanya jika bukan flag CLONE_NEWUSER
SCMP_CMP_LTarg < datum_anilai fd di bawah ambang tertentu
SCMP_CMP_LEarg <= datum_abatas atas sebuah nilai
SCMP_CMP_GTarg > datum_anilai melebihi ambang
SCMP_CMP_GEarg >= datum_abatas bawah sebuah nilai
SCMP_CMP_MASKED_EQ(arg & datum_a) == datum_bmemeriksa flag tertentu tanpa peduli bit lain

Contoh: Membatasi socket Hanya untuk AF_UNIX

Mari kita bangun filter yang lebih presisi: default-deny, tapi izinkan socket hanya jika domain-nya AF_UNIX (nilai 1). Socket AF_INET dan lainnya akan ditolak dengan EPERM.

filter_socket.c
#include <seccomp.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
 
int main(void) {
    scmp_filter_ctx ctx;
    int fd;
 
    /* 1. Default: tolak semua dengan EPERM */
    ctx = seccomp_init(SCMP_ACT_ERRNO(EPERM));
    if (ctx == NULL) {
        perror("seccomp_init");
        return 1;
    }
 
    /* 2. Izinkan socket hanya untuk domain AF_UNIX */
    if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), 1,
                         SCMP_CMP(0, SCMP_CMP_EQ, AF_UNIX)) != 0) {
        perror("seccomp_rule_add socket");
        return 1;
    }
 
    /* 3. Izinkan menulis ke stdout agar program bisa mencetak */
    if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 1,
                         SCMP_CMP(1, SCMP_CMP_EQ, STDOUT_FILENO)) != 0) {
        perror("seccomp_rule_add write");
        return 1;
    }
 
    /* 4. Izinkan keluar dengan bersih */
    if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0) != 0) {
        perror("seccomp_rule_add exit_group");
        return 1;
    }
 
    /* 5. Pasang filter */
    if (seccomp_load(ctx) != 0) {
        perror("seccomp_load");
        return 1;
    }
    seccomp_release(ctx);
 
    /* 6. Pengujian */
    fd = socket(AF_INET, SOCK_STREAM, 0);
    printf("socket AF_INET -> %s\n", fd < 0 ? strerror(errno) : "berhasil");
 
    fd = socket(AF_UNIX, SOCK_STREAM, 0);
    printf("socket AF_UNIX -> %s\n", fd < 0 ? strerror(errno) : "berhasil");
 
    return 0;
}

Mari bedah bagian arg filternya. Pada aturan socket:

c
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), 1,
                 SCMP_CMP(0, SCMP_CMP_EQ, AF_UNIX));
  • SCMP_SYS(socket) — syscall yang disaring.
  • 1 — jumlah kondisi argumen.
  • SCMP_CMP(0, SCMP_CMP_EQ, AF_UNIX) — argumen ke-0 (domain) harus sama dengan AF_UNIX.

Artinya: socket diizinkan hanya jika argumen pertamanya sama dengan AF_UNIX. Socket dengan domain lain jatuh ke default action EPERM.

Pada aturan write, kita melakukan hal yang sama untuk argumen ke-1 (fd):

c
SCMP_CMP(1, SCMP_CMP_EQ, STDOUT_FILENO)

write diizinkan hanya jika file descriptornya adalah stdout — tidak ada syscall lain yang bisa menulis ke file mana pun selain stdout. Ini contoh pola "least privilege" yang sangat ketat.

Kompilasi dan jalankan:

Kompilasi dan jalankan filter socket
gcc -o filter-socket filter_socket.c -lseccomp
./filter-socket
socket AF_INET -> Operation not permitted
socket AF_UNIX -> berhasil

Tip

Perhatikan output di atas: panggilan socket(AF_INET, ...) gagal dengan EPERM, sementara socket(AF_UNIX, ...) berhasil. Filter seccomp kalian sekarang membedakan dua panggilan ke syscall yang sama berdasarkan argumennya — inilah kekuatan arg filter.

Masked Equality: Membatasi Mode Akses File

Sekarang mari lihat SCMP_CMP_MASKED_EQ dalam aksi. Bayangkan kita ingin hanya mengizinkan openat dengan mode read-only. Pada Linux, bit 0-1 dari argumen flags adalah mode akses: O_RDONLY (0), O_WRONLY (1), O_RDWR (2). Kita bisa memblokir setiap openat yang meminta akses tulis:

Batasi openat hanya read-only
#include <fcntl.h>
 
/* deny-list: tolak openat yang meminta akses tulis */
/* (arg & (O_WRONLY|O_RDWR)) == 0 berarti read-only */
seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EACCES), SCMP_SYS(openat), 1,
                 SCMP_CMP(2, SCMP_CMP_MASKED_EQ,
                          O_WRONLY | O_RDWR, O_WRONLY | O_RDWR));

Bagian SCMP_CMP(2, SCMP_CMP_MASKED_EQ, O_WRONLY | O_RDWR, O_WRONLY | O_RDWR) berarti: jika (flags & (O_WRONLY | O_RDWR)) == (O_WRONLY | O_RDWR) — yaitu jika bit mode akses menandakan tulis — maka syscall ditolak. Karena kita memakai deny-list (default SCMP_ACT_ALLOW), openat dengan O_RDONLY tetap diizinkan.

Warning

Sekali lagi perlu ditegaskan: seccomp tidak bisa memeriksa isi path file yang dibuka, karena ia hanya membaca nilai argumen (pointer), bukan memori yang ditunjuk. Jika kebutuhan kalian adalah membatasi path tertentu secara isi, gunakan mekanisme berbasis path seperti Landlock atau AppArmor — seccomp fokus pada syscall dan argumennya.

Architecture Handling

Filter seccomp bersifat spesifik terhadap arsitektur: setiap arsitektur punya nomor syscall yang berbeda, dan program BPF harus tahu arsitektur mana yang sedang berjalan. Inilah kenapa seccomp_data menyertakan field arch.

Native ABI dan Arsitektur Lain

Ketika kalian membuat context dengan seccomp_init, libseccomp secara default mengikuti native ABI — arsitektur utama dari proses yang sedang berjalan (misalnya x86_64). Kalian bisa menambahkan arsitektur lain dengan seccomp_arch_add:

Daftarkan arsitektur target
#include <seccomp.h>
 
/* Context mengikuti arsitektur native (misal x86_64) */
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_ERRNO(EPERM));
 
/* Tambahkan dukungan untuk 32-bit i386 dan arm64 */
seccomp_arch_add(ctx, SCMP_ARCH_X86);
seccomp_arch_add(ctx, SCMP_ARCH_AARCH64);

Setiap aturan yang kalian tambahkan otomatis diterapkan ke semua arsitektur yang terdaftar — libseccomp menangani pemetaan nomor syscall antar arsitektur secara internal. Inilah alasan kalian tidak perlu menulis prolog pengecekan arsitektur dengan tangan (kita melihatnya saat disassemble di episode 4).

Bahaya Syscall Multiplexing x32

Sekarang bagian yang sering diabaikan tapi berbahaya: ABI x32. Linux x86_64 sebenarnya punya tiga ABI yang bisa dipakai satu proses:

  1. x86_64 — ABI 64-bit normal (nomor syscall standar).
  2. i386 — ABI 32-bit (nomor syscall 32-bit).
  3. x32 — ABI "32-bit pointer, 64-bit registers" yang memakai nomor syscall x86_64 tetapi dengan bit ke-30 disetel (misalnya read menjadi nomor syscall 0x40000000 + 0).

Bahayanya jelas: jika filter kalian mengizinkan read pada ABI x86_64, sebuah program jahat bisa memanggil syscall yang sama melalui ABI x32 — dan jika filter tidak memeriksa arsitektur, aturan itu mungkin di-bypass. Syscall multiplexing seperti ini adalah celah klasik dalam filter seccomp yang ditulis tangan. libseccomp menangani ini secara otomatis: ketika kalian mendaftarkan SCMP_ARCH_X86_64, libseccomp juga menambahkan handling untuk ABI x32 di prolog BPF — memastikan syscall x32 tidak lolos dari aturan yang ditujukan untuk x86_64. Inilah salah satu alasan terkuat untuk selalu memakai libseccomp, bukan menulis BPF manual.

Important

Jika kalian menemukan kode yang "menulis filter seccomp dengan BPF tangan" tanpa penanganan arsitektur, anggap filter itu tidak aman. Periksa selalu apakah arsitektur dicek (biasanya lewat instruksi A = arch pada program BPF) dan bagaimana ABI x32 diperlakukan. libseccomp melakukan semuanya ini secara otomatis.

Penutup

Di episode 5 ini kalian sudah menguasai dua kemampuan lanjutan:

  • Arg filter dengan SCMP_CMP: EQ, NE, LT, LE, GT, GE, dan MASKED_EQ — menyaring syscall berdasarkan nilai argumen.
  • Contoh nyata: membatasi socket hanya untuk AF_UNIX, dan membatasi write hanya ke stdout.
  • Architecture handling: native ABI, penambahan SCMP_ARCH_X86 / SCMP_ARCH_AARCH64, dan bahaya syscall multiplexing x32 yang ditangani otomatis oleh libseccomp.

Dengan kemampuan ini, kalian sudah bisa menulis filter seccomp yang presisi dan aman untuk aplikasi sendiri. Di episode 6 berikutnya kita akan membahas fitur paling modern: seccomp user notification (SECCOMP_RET_NOTIFY) — membiarkan supervisor di user-space mengambil keputusan atas syscall tertentu secara dinamis, dan bagaimana ini mengubah cara kita merancang sandbox. Sampai jumpa!

Belajar Seccomp - ArgFilter & Architecture Handling | Belajar Seccomp