Belajar Seccomp - Profile Container (Docker/runc)
Episode 8 of 23

Belajar Seccomp - Profile Container (Docker/runc)

Menjelajahi profil seccomp bawaan Docker (docker-default), daftar syscall yang diblokir, dan perilaku container yang melanggarnya. Termasuk format profil JSON untuk runtime OCI serta pembuatan profil kustom allowlist lewat --security-opt.

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

Pendahuluan

Di episode 7 kalian menguasai debugging filter: membongkar BPF, mengamati syscall, dan menguji deny behavior. Sekarang waktunya memakai semua itu di tempat yang paling banyak ditemui di dunia nyata: container. Container bukan mesin virtual — ia berbagi kernel yang sama dengan host. Dan ketika kernel itu dimiliki bersama, syscall berbahaya dari satu container bisa mengguncang semuanya.

Episode 8 ini membedah bagaimana Docker dan runtime OCI (runc/crun) menerapkan seccomp: profil bawaan docker-default, daftar syscall yang diblokir, format JSON profil, dan cara membangun profil kustom untuk aplikasi kalian.

Container dan Kernel yang Sama

Sebuah container adalah kumpulan proses di kernel host yang diisolasi dengan namespaces, cgroups, dan — bila diatur — seccomp. Analoginya seperti apartemen dalam satu gedung: setiap penghuni punya kamar sendiri (namespaces) dan jatah listrik sendiri (cgroups), tapi semuanya berbagi satu fondasi dan satu mesin listrik (kernel). Kalau satu penghuni bisa menyentuh saklar utama, semua penghuni terdampak.

Syscall seperti reboot, kexec_load, atau mount adalah "saklar utama" itu. Namespaces tidak menghalanginya — ia tetap masuk ke kernel yang sama. Seccomp-lah yang memutuskan syscall tersebut tidak boleh dipanggil sama sekali.

docker-default: Profil Bawaan

Sejak Docker 1.10, setiap container yang dijalankan otomatis menerima profil seccomp bernama docker-default — tanpa konfigurasi tambahan. Ini salah satu momen terpenting dalam sejarah keamanan container: seccomp jadi pertahanan default, bukan fitur yang diaktifkan manual.

Profil ini bekerja sebagai denylist: defaultAction-nya adalah SCMP_ACT_ERRNO(EPERM), artinya semua syscall diblokir kecuali yang eksplisit diizinkan. Daftar yang diblokir sekitar empat puluh syscall, dengan yang paling sering dikutip seperti ini:

LinuxSubset syscall yang diblokir docker-default
acct, add_key, bpf, clock_adjtime, clock_settime, create_module,
delete_module, finit_module, get_kernel_syms, init_module, io_cancel,
ioperm, iopl, kexec_file_load, kexec_load, keyctl, lookup_dcookie,
mbind, mount, move_pages, name_to_handle_at, nfsservctl,
open_by_handle_at, perf_event_open, personality, pivot_root,
process_vm_readv, process_vm_writev, ptrace, quotactl, reboot,
request_key, sethostname, setns, settimeofday, shmctl, sysfs,
swapon, swapoff, umount, umount2, unshare, uselib, userfaultfd

Lihat polanya, bukan sekadar menghafal daftarnya:

  • Manipulasi kernel: mount, umount, reboot, sethostname, swapon, kexec_load, init_module — menyentuh host secara langsung.
  • Eskalasi namespace: unshare, setns, pivot_root — membangun atau melompat ke namespace baru, jalur pelarian klasik.
  • Pengintaian proses lain: ptrace, perf_event_open, process_vm_readv, process_vm_writev, keyctl — membaca memori atau men-debug proses tetangga.
  • Sisa-sisa lawas: ioperm, iopl, vm86, sysfs, _sysctl — syscall usang yang tidak dibutuhkan aplikasi modern.

Dua hal yang perlu kalian tahu soal clone: docker-default tidak memblokir clone sepenuhnya, tapi memblokir pemanggilannya dengan flag pembuatan namespace tertentu (misalnya CLONE_NEWUSER). Di sinilah kekuatan argfilter dari episode 5 dipakai — memblokir syscall dengan argumen tertentu, bukan syscall itu sendiri.

Bila aplikasi memanggil syscall yang diblokir, ia akan menerima EPERM — aplikasi melihat "Operation not permitted" tanpa tahu siapa yang menolak. Inilah momen pertama kalian pakai keterampilan episode 7: konfirmasi dengan ausearch -m SECCOMP bahwa yang menolak adalah seccomp, bukan permission biasa.

Profil ini aktif secara otomatis. Untuk memastikan daemon Docker kalian mendukungnya, cek daftar SecurityOptions:

Cek dukungan seccomp di daemon Docker
docker info --format '{{.SecurityOptions}}'

Pastikan ada name=seccomp di daftarnya.

Note

Profil bisa dimatikan dengan --security-opt seccomp=unconfined, tapi itu sama saja melepas sabuk pengaman. Pertahankan seccomp default; jadikan unconfined hanya sebagai alat debugging sementara.

Format Profil OCI

Profil seccomp container bukan binary C, melainkan JSON yang mengikuti Open Container Initiative (OCI) runtime spec. runc, crun, containerd, dan CRI-O semuanya membaca format yang sama — kalian belajar sekali, berlaku di mana-mana. Struktur dasarnya: defaultAction, defaultErrnoRet, archMap, dan daftar syscalls:

profile-allowlist.json — syscall inti aplikasi web
{
  "defaultAction": "SCMP_ACT_ERRNO",
  "defaultErrnoRet": 1,
  "archMap": [
    {
      "architecture": "SCMP_ARCH_X86_64",
      "subArchitectures": ["SCMP_ARCH_X86", "SCMP_ARCH_X32"]
    }
  ],
  "syscalls": [
    {
      "names": [
        "read", "write", "readv", "writev", "openat", "close",
        "fstat", "lseek", "mmap", "mprotect", "munmap", "brk",
        "exit", "exit_group", "rt_sigaction", "clone", "execve",
        "getpid", "access", "fcntl", "futex", "clock_gettime",
        "nanosleep", "socket", "connect", "sendto", "recvfrom",
        "shutdown", "setsockopt", "getsockopt"
      ],
      "action": "SCMP_ACT_ALLOW"
    }
  ]
}

Membaca bagian-bagiannya:

  • defaultAction — apa yang terjadi pada syscall yang tidak tercantum. SCMP_ACT_ERRNO berarti ditolak dengan errno.
  • defaultErrnoRet — errno default (1 = EPERM). Di sinilah perilaku "deny" diatur secara global.
  • archMap — arsitektur yang valid beserta sub-arsitektur kompatibelnya. Perlu diisi agar profil tidak salah jalan saat binary 32-bit berjalan di kernel 64-bit.
  • syscalls — daftar aturan; setiap entri punya names, action, dan opsional args untuk aturan berbasis argumen (bentuk JSON dari argfilter episode 5).

Perhatikan ini adalah profil allowlist: kalian menulis syscall yang boleh, sisanya ditolak. Pendekatan ini jauh lebih ketat daripada denylist docker-default, dan itulah yang kalian inginkan untuk aplikasi yang dipahami perilakunya.

Memakai Profil di Runtime

Menyimpan profil sebagai file JSON, lalu menunjukkannya ke Docker lewat --security-opt:

Jalankan container dengan profil seccomp kustom
docker run --rm --security-opt seccomp=profile-allowlist.json nginx:1.27

Cara yang sama berlaku di Compose file (via security_opt) dan di runtime lain: containerd dan CRI-O pada akhirnya menurunkan semuanya ke config.json runc, dan objek seccomp di config tersebut adalah JSON yang sama persis. Kalian belajar satu format, dan format itu ikut ke mana pun.

Warning

Hati-hati dengan --security-opt seccomp=unconfined. Ini sangat berguna untuk debugging cepat — "apakah seccomp yang memblokir atau bukan" — tapi jangan pernah menjadi kondisi produksi yang permanen. Container tanpa seccomp berarti setiap syscall di host terbuka untuknya.

Membangun Profil Aplikasi Sendiri

Membangun profil dari nol terdengar menakutkan, padahal alurnya sudah kalian kuasai di episode 7:

  1. Baseline — jalankan aplikasi normal tanpa filter dan catat syscall-nya dengan strace -f -c.
  2. Terjemahkan ke allowlist — ubah ringkasan frekuensi syscall menjadi daftar names di JSON.
  3. Jangan lupa syscall startupbrk, mmap, mprotect, rt_sigaction, openat sering muncul di detik-detik pertama; aplikasi yang gagal boot biasanya kehilangan ini.
  4. Uji dengan LOG dulu — set defaultAction menjadi SCMP_ACT_LOG, jalankan di staging, dan baca ausearch -m SECCOMP untuk syscall yang belum masuk daftar.
  5. Balik ke ERRNO — setelah tidak ada syscall yang keluar daftar, kunci dengan SCMP_ACT_ERRNO.

Mulailah dari profil kecil dan perluas secara bertahap. Profil yang terlalu kurus membuat aplikasi crash di detik pertama; profil yang terlalu gemuk adalah denylist yang menyamar sebagai allowlist. Iterasi log-dulu-enforce-kemudian adalah keseimbangan yang benar.

Penutup

Di episode 8 ini kalian telah memahami bahwa container berbagi kernel host, sehingga syscall berbahaya wajib dipangkas — dan Docker sudah melakukannya secara default dengan profil docker-default yang memblokir puluhan syscall. Kalian bisa membaca format profil OCI (JSON dengan defaultAction, archMap, dan syscalls), menerapkannya lewat --security-opt seccomp=profile.json, serta membangun allowlist dari baseline strace.

Kunci yang harus dibawa pulang:

  • Container berbagi kernel — seccomp adalah pagar antar apartemen.
  • docker-default adalah denylist baseline; aplikasi kalian layak dapat allowlist.
  • Bangun profil dari strace, uji dengan LOG, baru kunci dengan ERRNO.

Profil JSON itu hebat, tapi bukan satu-satunya jalan. Banyak daemon berjalan langsung di host tanpa container — dan mereka juga butuh seccomp. Di episode 9, kita masuk ke Seccomp di systemd & Service Units: SystemCallFilter, SystemCallArchitectures, dan RestrictAddressFamilies untuk mengunci service tanpa mengubah satu baris kode aplikasi.

Belajar Seccomp - Profile Container (Docker/runc) | Belajar Seccomp