Belajar Seccomp - Seccomp di systemd & Service Units
Episode 9 of 23

Belajar Seccomp - Seccomp di systemd & Service Units

Mengamankan service unit dengan sandboxing seccomp bawaan systemd: SystemCallFilter, SystemCallArchitectures, dan RestrictAddressFamilies. Dipadukan dengan NoNewPrivileges dan ProtectSystem, penguatan dilakukan dari unit file tanpa mengubah kode aplikasi.

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

Pendahuluan

Di episode 8 kalian mengunci container lewat profil JSON. Tapi tidak semua aplikasi hidup di container — banyak daemon penting (database, service kecil, tool internal) berjalan langsung di host sebagai service systemd. Untuk mereka, menulis profil JSON tidak praktis; yang dibutuhkan adalah cara mengunci syscall langsung dari unit file.

Episode 9 ini membahas sandboxing seccomp bawaan systemd: SystemCallFilter, SystemCallArchitectures, dan RestrictAddressFamilies, dipadukan dengan NoNewPrivileges dan ProtectSystem. Keunggulannya tegas: kalian memperketat service tanpa mengubah kode aplikasi, cukup dari file unit.

systemd sebagai Penerjemah seccomp

systemd tidak mengirim syscall sendiri — ia menerjemahkan direktif unit menjadi filter BPF yang dimuat ke kernel saat service dimulai. SystemCallFilter=@system-service bukan sekadar teks; di belakang layar systemd membangun program BPF yang sama persis seperti yang kalian tulis dengan libseccomp, lalu menerapkannya pada proses service.

Ada satu kunci fundamental yang membuat seluruh sandboxing ini bisa dicabut: no_new_privs. systemd menetapkannya lewat NoNewPrivileges=true — flag kernel yang memastikan proses tidak bisa menaikkan privilege (misalnya lewat setuid binary) dan sekaligus memungkinkan filter seccomp tetap berlaku tanpa hak istimewa. Tanpa itu, filter yang menolak setuid bisa di-bypass.

Note

Perhatikan: systemd menerapkan sejumlah syscall yang dianggap merusak untuk semua service secara default, bahkan sebelum kalian menyentuh SystemCallFilter. Direktif ini bukan fitur opsional yang "kadang-kadang aktif" — ia bekerja terus-menerus, dan kalian memperluasnya sesuai kebutuhan.

SystemCallFilter: Memilih dan Menolak Syscall

Direktif SystemCallFilter menerima daftar syscall dan/atau grup yang diawali @. Tanpa prefiks ~, direktif berarti allowlist (hanya syscall itu yang boleh); dengan prefiks ~, berarti denylist:

LinuxSystemCallFilter dalam dua mode
[Service]
# Allowlist: hanya syscall dalam grup ini yang boleh dipanggil
SystemCallFilter=@system-service
 
# Denylist: semua boleh, kecuali grup yang diberi tanda ~
SystemCallFilter=~@reboot @swap @obsolete

Grup dengan awalan @ mempermudah hidup kalian — satu nama mewakili puluhan syscall. Yang paling sering dipakai:

  • @system-service — set syscall yang wajar untuk service umum; titik awal yang aman untuk kebanyakan daemon.
  • @file-system — operasi filesystem seperti openat, unlink, rename.
  • @network-io — syscall jaringan seperti socket, connect, accept.
  • @privileged — syscall yang butuh privilege, sebagian besar tidak dibutuhkan service biasa.
  • @obsolete, @reboot, @swap — kandidat utama untuk diblokir.

Saat syscall diblokir, service menerima errno EPERM secara default. Kalian bisa mengubahnya dengan SystemCallErrorNumber=. Nilai defaultnya sudah masuk akal untuk sebagian besar kasus.

Tip

Mulailah dengan SystemCallFilter=@system-service sebagai allowlist, lalu jalankan service di staging. Bila service memanggil syscall yang tidak ada di grup, systemd mencatat kegagalannya di log service — baca dengan journalctl -u nama.service dan tambahkan syscall atau grup yang hilang. Pola log-dulu-tingkatkan ini sama persis dengan alur episode 7.

SystemCallArchitectures: Kunci Gerbang Arsitektur

Direktif SystemCallArchitectures mengontrol arsitektur syscall yang boleh dipanggil service. Nilai defaultnya native — hanya arsitektur asli kernel yang diizinkan. Ini penting karena kernel 64-bit bisa menerima syscall dari binary 32-bit (mode compat), dan celah di jalur compat itu pernah menjadi sumber bug keamanan:

LinuxHanya arsitektur native yang diizinkan
[Service]
SystemCallArchitectures=native

Selama service kalian tidak perlu menjalankan binary 32-bit, biarkan nilai ini. Setiap syscall dari arsitektur lain langsung ditolak tanpa peduli aturan SystemCallFilter — pagar luar sebelum pagar dalam.

RestrictAddressFamilies: Mengunci Keluarga Socket

Kembali ke episode 5: seccomp bisa menyaring argumen syscall, dan socket() punya argumen pertama — address family. systemd mengabstraksikannya dengan RestrictAddressFamilies:

LinuxHanya socket Unix dan TCP/IP yang boleh dibuka
[Service]
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6

Service hanya bisa membuka socket dengan keluarga yang terdaftar. AF_PACKET (raw packet), AF_NETLINK (komunikasi dengan kernel), dan AF_UNIX yang tidak diizinkan semuanya diblokir. Untuk daemon yang hanya melayani TCP, daftar singkat ini sudah lebih dari cukup — dan memotong jalur eksfiltrasi data lewat protokol lain.

Sandboxing Lengkap: Contoh Unit

Sekarang gabungkan semua direktif seccomp dengan sandboxing filesystem systemd dalam satu unit yang realistis:

Linuxwebapp.service — unit dengan sandboxing seccomp
[Unit]
Description=Layanan webapp yang diperketat
 
[Service]
ExecStart=/usr/local/bin/webapp
User=webapp
Group=webapp
Restart=on-failure
 
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
RestrictSUIDSGID=true
 
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallFilter=~@reboot @swap @obsolete
SystemCallErrorNumber=EPERM
 
[Install]
WantedBy=multi-user.target

Membaca unit ini lapis demi lapis:

  • NoNewPrivileges=true — kunci utama; tanpa ini, sebagian direktif seccomp tidak bisa dijamin.
  • ProtectSystem=strict — seluruh / menjadi read-only untuk service; hanya path yang diizinkan eksplisit yang bisa ditulis.
  • ProtectHome=true — direktori home tidak terlihat.
  • RestrictAddressFamilies, SystemCallArchitectures, SystemCallFilter — tiga lapis seccomp: keluarga socket, arsitektur, dan daftar syscall.

Perhatikan yang tidak berubah: ExecStart sama seperti biasa, aplikasi tidak tahu apa-apa. Seluruh penguatan datang dari unit file — ini kekuatan utama pendekatan systemd.

Verifikasi: systemd-analyze

Mengklaim unit lebih aman itu mudah; membuktikannya butuh alat. systemd-analyze security menilai paparan unit dan menunjukkan direktif mana yang sudah diterapkan:

Skor paparan dan status sandboxing
systemd-analyze security webapp.service

Contoh keluaran (dipotong):

Keluaran systemd-analyze security
  NAME                        DESCRIPTION                    EXPOSURE
  PrivateNetwork=no           Service has access to network  0.5
  RestrictAddressFamilies=yes Address family restricted      0.1
  SystemCallFilter=yes        System call allowlisted        0.1
  NoNewPrivileges=yes         Privilege escalation prevented 0.1
→ Overall exposure level for webapp.service: 1.2

Semakin rendah skor eksposure, semakin tertutup service-nya. Verifikasi juga status aktual service dengan systemctl show webapp.service untuk memastikan direktif benar-benar aktif, bukan hanya tertulis.

Warning

Sandboxing systemd bukan alasan untuk mengabaikan seccomp lain. ProtectSystem dan SystemCallFilter memeriksa dimensi yang berbeda: satu membatasi akses filesystem, satu membatasi aksi terhadap kernel. Kombinasikan keduanya untuk defense in depth — seperti memasang kunci pintu dan alarm secara bersamaan, bukan memilih salah satu.

Catatan Praktik

Beberapa hal yang menyelamatkan kalian di produksi:

  • Perketat bertahap. Terapkan SystemCallFilter satu level per rilis. Service yang berhenti setelah perketatan adalah sinyal syscall yang hilang, bukan alasan untuk menyerah.
  • Ambil baseline dulu. Jalankan service di staging dengan strace -f -c sebelum menyusun SystemCallFilter — hasilnya jadi peta syscall yang sebenarnya.
  • Denylist vs allowlist. Untuk service yang sudah kalian kenal, allowlist (@system-service + tambahan) lebih aman. Denylist (~@reboot dll.) hanya untuk menambal yang jelas-jelas berbahaya.
  • Jangan lupa unit lain. Worker cron, socket-activated service, dan daemon kecil sering terlupakan — semua sama-sama butuh konfigurasi ini.

Penutup

Di episode 9 ini kalian telah memahami bahwa systemd menerjemahkan direktif unit menjadi filter seccomp: SystemCallFilter untuk daftar syscall dan grup @, SystemCallArchitectures=native untuk menutup jalur arsitektur compat, dan RestrictAddressFamilies untuk mengunci keluarga socket. Dipadukan dengan NoNewPrivileges dan ProtectSystem, satu file unit bisa mengubah daemon biasa menjadi service yang jauh lebih sulit ditembus — tanpa menyentuh kode aplikasi.

Kunci yang harus dibawa pulang:

  • Sandboxing systemd = seccomp di level unit; aplikasi tidak perlu diubah.
  • SystemCallArchitectures=native adalah pagar luar yang hampir selalu gratis.
  • Buktikan dengan systemd-analyze security, bukan sekadar klaim di unit file.

Semua lapisan sejauh ini diterapkan per-mesin: per container, per service. Bagaimana mengelolanya ketika ada ratusan mesin? Di episode 10, kita masuk ke Seccomp di Kubernetes: field seccompProfile di securityContext, evolusi dari annotation alpha, Pod Security Standards, dan distribusi profil kustom antar node.