Menjalankan kode yang tidak tepercaya (untrusted code) di dalam infrastruktur multi-tenant memerlukan kontrol isolasi tingkat kernel. Tanpa kontrol ketat, beban kerja komputasi dapat memicu resource exhaustion, melakukan kripto-mining, mengeksekusi timing side-channel attacks (seperti Spectre), hingga melakukan container escape. Mengamankan sistem ini menuntut isolasi deterministik melalui tiga pilar: pembatasan akses syscall via Seccomp/KVM, isolasi waktu eksekusi (deterministic clock), dan penegakan batas konsumsi sumber daya via cgroups v2.
1. Isolasi Syscall dengan Seccomp BPF
Sebagian besar interaksi berbahaya dengan host kernel berakar pada syscall yang tidak perlu diekspos ke workload simulasi. Menggunakan allowlist berbasis Seccomp BPF membatasi attack surface kernel secara langsung.
Filter seccomp default harus menolak semua syscall (SCMP_ACT_KILL_PROCESS atau SCMP_ACT_ERRNO(EPERM)) dan hanya mengizinkan syscall minimal untuk eksekusi memori, I/O dasar, dan kontrol alur thread.
#define _GNU_SOURCE
#include <seccomp.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
int apply_deterministic_seccomp(void) {
// Kill thread jika mencoba syscall di luar allowlist
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL_PROCESS);
if (!ctx) return -ENOMEM;
const int allowed_syscalls[] = {
SCMP_SYS(read),
SCMP_SYS(write),
SCMP_SYS(exit_group),
SCMP_SYS(brk),
SCMP_SYS(mmap),
SCMP_SYS(munmap),
SCMP_SYS(mprotect),
SCMP_SYS(futex),
};
for (size_t i = 0; i < sizeof(allowed_syscalls)/sizeof(int); i++) {
if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, allowed_syscalls[i], 0) < 0) {
seccomp_release(ctx);
return -1;
}
}
// Blokir syscall pembacaan wall-clock resolusi tinggi
// Ubah status ke error agar binary tidak crash langsung jika runtime memanggilnya
seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(clock_gettime), 0);
seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(gettimeofday), 0);
int ret = seccomp_load(ctx);
seccomp_release(ctx);
return ret;
}Jika workload membutuhkan isolasi lebih kuat dari boundary namespace Linux murni, gunakan microVM berbasis KVM (misalnya Firecracker atau Cloud Hypervisor) untuk menempatkan guest kernel independen di atas perangkat keras fisik.
2. Memitigasi Timing Side-Channel dengan Virtual Deterministic Clock
Penyerang menggunakan instruksi waktu resolusi tinggi seperti RDTSC pada x86_64 atau syscall clock_gettime untuk mengukur variasi latensi cache CPU saat mengeksekusi serangan cache-timing. Isolasi deterministik mewajibkan penghapusan akses waktu nyata.
Menonaktifkan RDTSC dan Memanipulasi vdso
Pada Linux, pemanggilan clock_gettime dialihkan melalui mekanisme vDSO (virtual Dynamic Shared Object) yang mengeksekusi langsung tanpa context switch ke kernel. Untuk memitigasi celah ini:
- Nonaktifkan vDSO via kernel boot parameter:
vdso=0. - Gunakan flag
prctl(PR_SET_TSC, PR_TSC_SIGSEGV)untuk memotong eksekusi langsung instruksiRDTSCdan mengalihkannya ke sinyal handler deterministik. - Dalam microVM berbasis KVM, pasang flag emulasi timer konstan atau nonaktifkan instruksi
RDTSClangsung ke host hardware (CR4.TSD = 1).
#include <sys/prctl.h>
#include <signal.h>
#include <ucontext.h>
#include <stdint.h>
static uint64_t simulated_cycles = 0;
void handle_sigsegv(int sig, siginfo_t *info, void *ucontext) {
ucontext_t *ctx = (ucontext_t *)ucontext;
// Deteksi opcode RDTSC: 0x0F 0x31
uint8_t *pc = (uint8_t *)ctx->uc_mcontext.gregs[REG_RIP];
if (pc[0] == 0x0F && pc[1] == 0x31) {
simulated_cycles += 1000; // Kembalikan step waktu sintetis tetap
ctx->uc_mcontext.gregs[REG_RAX] = simulated_cycles & 0xFFFFFFFF;
ctx->uc_mcontext.gregs[REG_RDX] = (simulated_cycles >> 32) & 0xFFFFFFFF;
ctx->uc_mcontext.gregs[REG_RIP] += 2; // Lewati instruksi
return;
}
_exit(128 + sig);
}
void trap_rdtsc(void) {
struct sigaction sa = { .sa_sigaction = handle_sigsegv, .sa_flags = SA_SIGINFO };
sigaction(SIGSEGV, &sa, NULL);
prctl(PR_SET_TSC, PR_TSC_SIGSEGV);
}3. Kontrol Resource Budget Menggunakan cgroups v2
Mencegah komputasi berlebih memerlukan pembatasan sumber daya yang ketat. cgroups v2 menyatukan hierarki resource management dan menyediakan metrik yang lebih dapat diandalkan dibanding v1.
Konfigurasi Quota CPU dan OOM Handling
Atur parameter batas komputasi langsung di direktori cgroup v2 sandbox target:
# Buat path cgroup spesifik untuk instance sandbox
mkdir -p /sys/fs/cgroup/sandbox-pool/instance-01
# Berikan kuota CPU: 200000 mikrosekon per periode 100000 mikrosekon (maks 2 core)
echo "200000 100000" > /sys/fs/cgroup/sandbox-pool/instance-01/cpu.max
# Batasi memori fisik dan swap secara absolut
echo "536870912" > /sys/fs/cgroup/sandbox-pool/instance-01/memory.max # 512 MiB
echo "0" > /sys/fs/cgroup/sandbox-pool/instance-01/memory.swap.max # Matikan swap
# Terapkan isolasi file descriptor dan process spawning (pencegahan fork-bomb)
echo "64" > /sys/fs/cgroup/sandbox-pool/instance-01/pids.max
# Pindahkan runner ke cgroup
echo $RUNNER_PID > /sys/fs/cgroup/sandbox-pool/instance-01/cgroup.procs4. Trade-off dan Pertimbangan Arsitektur
Mengimplementasikan determinisme penuh memiliki konsekuensi langsung terhadap kompatibilitas runtime target:
- Runtime Modern: Garbage Collector (Go, Node.js, JVM) sangat bergantung pada monotonic timer untuk penjadwalan internal. Menghapus
clock_gettimesepenuhnya dapat menyebabkan thread starvation. Sebagai solusi, terapkan LD_PRELOAD interposition library untuk memetakan syscall waktu ke penghitung siklus instruksi logis. - Performa: Menangkap instruksi
RDTSCvia KVM interception atau sinyal Linux menghasilkan penalty overhead eksekusi. Batasi determinisme ketat hanya pada runner yang mengisolasi kode asing, bukan infrastruktur orkestrasi internal.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!