Penutup chapter namespaces meninggalkan satu ganjalan: di dalam container proses kita tampak sebagai root, tetapi jelas tidak bisa memformat disk host atau memuat modul kernel. Sebaliknya, banyak program hanya membutuhkan satu hak kecil dari root — web server cukup hak untuk bind port 80 — namun secara tradisional harus dijalankan sebagai root penuh dengan ratusan hak lain yang menyertainya. Linux memecah dilema ini dengan memecah root itu sendiri: privilege root diurai menjadi sekitar empat puluh satuan bernama capability, dan proses boleh membawa beberapa satuan saja. Pada chapter ini kita pelajari daftar satuan tersebut, membaca statusnya dari /proc, lalu memberi capability langsung ke sebuah biner dengan setcap.

Masalah: Satu Kunci untuk Semua Pintu #

Model permission klasik Unix mengenal dua dunia: user biasa dan UID 0. Begitu sebuah program berjalan sebagai UID 0, kernel tidak lagi memeriksa permission file, pembatasan port, maupun kuota resource — semuanya terbuka sekaligus. Model serba-atau-tidak-apa-apa ini berbahaya karena satu celah kecil pada program root langsung membuka seluruh sistem: ping misalnya hanya butuh membuat socket mentah (raw socket), tetapi karena dulu hanya root yang boleh, biner ping dijadikan setuid root — dan setiap bug di dalamnya menjadi pintu masuk ke seluruh mesin. Capability memperbaiki model ini dengan mengganti pertanyaan “apakah Anda root?” menjadi “capability mana yang Anda pegang?” — kernel memeriksa satuan yang relevan pada setiap operasi.

Root yang Dipecah menjadi Satuan Capability #

Sejak kernel 2.2, setiap pemeriksaan privilege di dalam kernel memakai satuan capability, dan “root penuh” kini hanyalah proses yang memegang semua satuan tersebut. Daftar lengkap 41 satuan beserta operasi yang dijaganya terdokumentasi di capabilities(7) ; berikut yang paling sering kita jumpai sehari-hari:

CapabilityOperasi yang dijaga
CAP_NET_BIND_SERVICEbind port di bawah 1024
CAP_NET_RAWsocket mentah — kebutuhan klasik ping
CAP_NET_ADMINmengubah interface, routing, firewall
CAP_DAC_OVERRIDEmelewati pemeriksaan permission file
CAP_SYS_ADMINmount, pembuatan namespace, dan ratusan hal lain
CAP_KILLmengirim sinyal ke proses milik user lain

Setiap proses menyimpan capability-nya dalam beberapa set yang berbeda peran: effective (yang sedang dipakai kernel saat memeriksa), permitted (persediaan yang boleh dinaikkan ke effective), dan bounding (batas atas yang tidak mungkin dilewati walau program mencoba menaikkan haknya). Status ketiganya dapat kita baca langsung dari procfs.

Membaca Bitmask Capability dari /proc/self/status #

Field CapEff dan CapBnd pada /proc/self/status menyimpan set effective dan bounding sebagai bitmask heksadesimal — bit ke-10 misalnya berarti CAP_NET_BIND_SERVICE. Karena shell kita berjalan sebagai user biasa, bitmasknya nol; bandingkan dengan proses yang menjadi “root” di dalam user namespace:

bash
printf 'shell biasa    CapEff: %s\n' "$(grep CapEff /proc/self/status | cut -f2)"
printf 'root di userns CapEff: %s\n' "$(unshare -Ur grep CapEff /proc/self/status | cut -f2)"

Output yang Dihasilkan:

text
shell biasa    CapEff: 0000000000000000
root di userns CapEff: 000001ffffffffff

Nilai nol pada baris pertama menandakan shell kita tidak memegang satu capability pun. Nilai kedua menunjukkan proses di dalam user namespace memegang seluruh 41 bit — ingat chapter namespaces: “root” di sana hanya berlaku di dalam kaca, dan kernel tetap memakai nilai ini saat proses menyentuh resource milik namespace-nya sendiri. Bitmask mentah sulit dibaca manusia, jadi program kecil berikut menerjemahkannya menjadi daftar nama:

python
#!/usr/bin/env python3
"""Dekode bitmask capability pada /proc/self/status menjadi nama."""
NAMA_CAP = [
    "cap_chown", "cap_dac_override", "cap_dac_read_search",   # bit 0-2
    "cap_fowner", "cap_fsetid", "cap_kill",                   # bit 3-5
    "cap_setgid", "cap_setuid", "cap_setpcap",                # bit 6-8
    "cap_linux_immutable", "cap_net_bind_service",            # bit 9-10
    "cap_net_broadcast", "cap_net_admin", "cap_net_raw",       # bit 11-13
    "cap_ipc_lock", "cap_ipc_owner", "cap_sys_module",        # bit 14-16
    "cap_sys_rawio", "cap_sys_chroot", "cap_sys_ptrace",     # bit 17-19
    "cap_sys_pacct", "cap_sys_admin", "cap_sys_boot",         # bit 20-22
    "cap_sys_nice", "cap_sys_resource", "cap_sys_time",       # bit 23-25
    "cap_sys_tty_config", "cap_mknod", "cap_lease",           # bit 26-28
    "cap_audit_write", "cap_audit_control", "cap_setfcap",    # bit 29-31
    "cap_mac_override", "cap_mac_admin", "cap_syslog",        # bit 32-34
    "cap_wake_alarm", "cap_block_suspend", "cap_audit_read",  # bit 35-37
    "cap_perfmon", "cap_bpf", "cap_checkpoint_restore",       # bit 38-40
]


def baca_bitmask(bidang):
    """Ambil nilai heksadesimal field tertentu dari /proc/self/status."""
    with open("/proc/self/status") as f:
        for baris in f:
            if baris.startswith(bidang + ":"):
                return int(baris.split()[1], 16)
    return 0


def dekode(nilai):
    """Ubah bitmask integer menjadi daftar nama capability."""
    hasil = []
    for i, nama in enumerate(NAMA_CAP):
        if nilai >> i & 1:
            hasil.append(nama)
    return hasil


def tampil(label, daftar):
    if not daftar:
        print(f"{label}: (kosong)")
        return
    print(f"{label}: {len(daftar)} capability")
    for nama in daftar:
        print(f"  {nama}")


tampil("CapEff (efektif)", dekode(baca_bitmask("CapEff")))
tampil("CapBnd (bounding)", dekode(baca_bitmask("CapBnd")))

Program di atas tersimpan sebagai decode-cap.py pada bundle chapter ini. Jalankan dengan:

bash
python3 decode-cap.py

Output yang Dihasilkan:

text
CapEff (efektif): (kosong)
CapBnd (bounding): 14 capability
  cap_chown
  cap_dac_override
  cap_fowner
  cap_fsetid
  cap_kill
  cap_setgid
  cap_setuid
  cap_setpcap
  cap_net_bind_service
  cap_net_raw
  cap_sys_chroot
  cap_mknod
  cap_audit_write
  cap_setfcap

Mari kita bedah cara kerjanya:

  1. NAMA_CAP: daftar nama capability menurut urutan bitnya di bitmask — urutan ini persis mengikuti tabel pada capabilities(7), sehingga indeks i di daftar sama dengan nomor bit di kernel.
  2. baca_bitmask(): membaca /proc/self/status baris demi baris; saat menemukan field yang dicari, teks heksadesimalnya dikonversi dengan int(nilai, 16).
  3. dekode(): ekspresi nilai >> i & 1 menggeser bitmask i bit lalu menyaring bit paling kanan — hasilnya 1 bila capability nomor i aktif.
  4. tampil(): mencetak daftar hasil dekode; set CapEff kita kosong karena user biasa, sedangkan CapBnd berisi 14 capability.

File Capability: Menyematkan Hak pada Biner #

Set kedua effective dan permitted diisi ulang setiap kali proses mengeksekusi program baru, dan salah satu sumbernya adalah file capability — extended attribute bernama security.capability yang tersimpan menempel pada file biner. Perintah setcap menyematkannya; getcap membacanya kembali:

bash
unshare -Urm bash -c '
  mkdir -p latih-mnt
  mount -t tmpfs tmpfs latih-mnt
  cp /bin/true latih-mnt/server-kecil
  /usr/sbin/setcap cap_net_bind_service=+ep latih-mnt/server-kecil
  /usr/sbin/getcap latih-mnt/server-kecil
'

Output yang Dihasilkan:

text
latih-mnt/server-kecil cap_net_bind_service=ep

Bedah blok di atas langkah demi langkah:

  1. unshare -Urm bash -c '...': seperti pada chapter namespaces, kita meminjam user namespace (-Ur) dan mount namespace (-m) agar bisa bekerja sebagai “root” tanpa root sungguhan.
  2. mount -t tmpfs tmpfs latih-mnt: setcap membutuhkan CAP_SETFCAP atas filesystem tempat file berada — tmpfs di dalam namespace kita memenuhinya, sementara filesystem sandbox utama milik host tidak.
  3. cp /bin/true latih-mnt/server-kecil: biner apa pun boleh; di sini kita menyalin program kecil sebagai contoh biner “server”.
  4. setcap cap_net_bind_service=+ep: operator +ep menambahkan capability ke set effective dan permitted biner tersebut; sejak detik itu siapa pun yang mengeksekusinya diberi CAP_NET_BIND_SERVICE — cukup untuk bind port 80 tanpa root.
  5. getcap: memastikan atribut telah menempel, terbaca sebagai cap_net_bind_service=ep.

Capabilities di Dunia Container #

Kombinasi namespaces + cgroups + capabilities inilah mesin keamanan container: runtime seperti Docker memulai container dengan melepas semua capability lalu menambahkan kembali daftar kecil yang aman. Parameter --cap-drop=ALL dan --cap-add=NET_BIND_SERVICE pada docker run memanipulasi himpunan tersebut secara langsung, dan perilakunya terdokumentasi pada referensi Docker run . Untuk memeriksa set yang aktif, alat capsh --print milik paket libcap2-bin merangkum kelima set sekaligus — berikut contoh tampilannya pada sesi user biasa:

text
Current: =
Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,
    cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,
    cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
Ambient set =
uid=1000(budi) euid=1000(budi)

Perhatikan kesamaan pola dengan output decode-cap.py kita: baris Bounding set berisi 14 nama yang sama. Praktik paling aman untuk layanan produksi adalah merunut satu per satu: mulai dari --cap-drop=ALL, lalu tambahkan capability minimum yang membuat aplikasi berjalan — filosofi yang sama dengan principle of least privilege pada chapter security hardening , kini dalam satuan kernel.

Satu kejanggalan masih tersisa dari perjalanan kita: proses “root” di user namespace ternyata tetap tidak bisa seenaknya memakai memori host sepuasnya. Pembatasan itu dijaga mekanisme lain — pengelola memori kernel, lengkap dengan page fault, page cache, dan hakimnya yang bernama OOM killer — yang menjadi topik chapter berikutnya.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

decode-cap.py

Unduh berkas
python
#!/usr/bin/env python3
"""Dekode bitmask capability pada /proc/self/status menjadi nama."""
NAMA_CAP = [
    "cap_chown", "cap_dac_override", "cap_dac_read_search",   # bit 0-2
    "cap_fowner", "cap_fsetid", "cap_kill",                   # bit 3-5
    "cap_setgid", "cap_setuid", "cap_setpcap",                # bit 6-8
    "cap_linux_immutable", "cap_net_bind_service",            # bit 9-10
    "cap_net_broadcast", "cap_net_admin", "cap_net_raw",      # bit 11-13
    "cap_ipc_lock", "cap_ipc_owner", "cap_sys_module",        # bit 14-16
    "cap_sys_rawio", "cap_sys_chroot", "cap_sys_ptrace",     # bit 17-19
    "cap_sys_pacct", "cap_sys_admin", "cap_sys_boot",         # bit 20-22
    "cap_sys_nice", "cap_sys_resource", "cap_sys_time",       # bit 23-25
    "cap_sys_tty_config", "cap_mknod", "cap_lease",           # bit 26-28
    "cap_audit_write", "cap_audit_control", "cap_setfcap",    # bit 29-31
    "cap_mac_override", "cap_mac_admin", "cap_syslog",        # bit 32-34
    "cap_wake_alarm", "cap_block_suspend", "cap_audit_read",  # bit 35-37
    "cap_perfmon", "cap_bpf", "cap_checkpoint_restore",       # bit 38-40
]


def baca_bitmask(bidang):
    """Ambil nilai heksadesimal field tertentu dari /proc/self/status."""
    with open("/proc/self/status") as f:
        for baris in f:
            if baris.startswith(bidang + ":"):
                return int(baris.split()[1], 16)
    return 0


def dekode(nilai):
    """Ubah bitmask integer menjadi daftar nama capability."""
    hasil = []
    for i, nama in enumerate(NAMA_CAP):
        if nilai >> i & 1:
            hasil.append(nama)
    return hasil


def tampil(label, daftar):
    if not daftar:
        print(f"{label}: (kosong)")
        return
    print(f"{label}: {len(daftar)} capability")
    for nama in daftar:
        print(f"  {nama}")


tampil("CapEff (efektif)", dekode(baca_bitmask("CapEff")))
tampil("CapBnd (bounding)", dekode(baca_bitmask("CapBnd")))