seccomp-bpf filter attack surface¶
seccomp-bpf, syscall'ları classic-BPF program'larıyla filtreleyen bir sandbox modelidir; attack surface'i filter'ın kendisindeki gap'lerden doğar — pointer target'ı görememekten kaynaklanan TOCTOU, eksik syscall/architecture coverage (x86-64 vs x32 vs compat) ve default-allow policy'ler klasik bypass class'larıdır.
Mechanism¶
Note
seccomp-bpf, her syscall'da kernel'e verilen bir classic-BPF program'ını çalıştırır. Program input olarak seccomp_data struct'ını görür: nr (syscall number), arch (AUDIT_ARCH_*) ve altı argument register (args[0..5]). Filter bir action döndürür (SECCOMP_RET_ALLOW, SECCOMP_RET_ERRNO, SECCOMP_RET_KILL_*, SECCOMP_RET_TRACE, SECCOMP_RET_LOG, SECCOMP_RET_USER_NOTIF).
Invariant / kısıt: BPF program'ları pointer dereference edemez. Kernel dokümantasyonunun deyişiyle: "BPF programs may not dereference pointers which constrains all filters to solely evaluating the system call arguments directly." Bunun sebebi bir TOCTOU race'idir — filter bir user pointer'ın değerini görebilir ama işaret ettiği belleği güvenli okuyamaz, çünkü user space, check ile use arasında pointed-to data'yı değiştirebilir. Attack surface tam da bu üç yapısal gap'ten çıkar:
- Argument-inspection TOCTOU — filter bir user pointer'ın hedefini denetlemeye çalışırsa (örn.
connect()sockaddr'ı ya daexecve()path'i), aynı bellek en az iki kez okunur; user thread araya girip değeri swap edebilir. Bu yüzden pointed-to content üzerinden alınan her policy kararı unsound'dur. - Incomplete coverage / architecture confusion — filter
nr'ıarchkontrol etmeden filtrelerse, ikinci bir ABI (x86-64 üstünde x32 ya da i386 compat) aynı işi farklı syscall number ile yaptırıp filtreyi atlar. x32,AUDIT_ARCH_X86_64altında__X32_SYSCALL_BITset edilerek çağrılır; policy bu bit'i ele almazsa bypass olur. - Permissive default-allow — allowlist yerine kısmi denylist kuran ya da default action'ı
ALLOWbırakan policy, filter'a eklenmemiş her syscall'a (socket'e karşısocketcall,open'a karşıopenat, yeni eklenen number'lar) sessizce izin verir.
Walkthrough¶
Aşağıdaki adımlar tamamı public/patched material'e dayanan konsept-seviyesi bir bypass akışıdır; weaponize edilmiş exploit değildir.
Step 1 — Policy'nin coverage'ını enumerate et
# Hedef process'in aktif filter mode'unu oku
grep Seccomp /proc/<pid>/status # 0=off 1=strict 2=filter
# Hangi syscall'ların ALLOW döndüğünü haritalamak için,
# bilinen number'ları tek tek çağırıp errno vs kill ayrımını gözle
Step 2 — Alternate ABI ile architecture gap'ini test et
// x86-64 filter yalnızca native `nr` değerlerini denyliste alıyorsa,
// aynı işlevi x32 ya da i386 compat number'ıyla çağırmak filtreyi atlar.
// (Filter `data->arch` ve `__X32_SYSCALL_BIT`'i kontrol etmiyorsa.)
syscall(__X32_SYSCALL_BIT | <nr>, ...); // konsept
Kernel man page: "filtering on system call number without checking the architecture value" en büyük pitfall'dır.
Step 3 — Pointer-hedefli policy'de TOCTOU'yu düşün
# Bir filter `connect()` addr'ının target'ını "kontrol ettiğini" varsayarsa
# (deep argument inspection), addr bellek sayfasını başka bir thread'le
# race'leyerek check-sonrası/use-öncesi değeri değiştirmek mümkündür.
# Bu yüzden native seccomp-bpf pointed-to content üzerinden filtre YAPMAZ;
# yapmaya çalışan out-of-tree/interposition katmanları klasik TOCTOU'ya açıktır.
Step 4 — Kalan primitive ile hedefe ulaş
Bypass edilen syscall (örn. execve/ptrace/keyctl) sandbox invariant'ını kırar
→ policy'nin engellemeyi umduğu davranış (yeni process, kernel R/W surface, vb.)
artık erişilebilir.
Warning
Bu class'ların hiçbiri bir memory-corruption "0-day" değildir; policy tasarım hatalarıdır. Gerçek risk, sandbox author'ının seccomp-bpf'in ne yapıp yapamayacağını yanlış modellemesinden doğar — özellikle "pointer'ı denetliyorum" ya da "tek ABI var" varsayımlarından.
Detection¶
SECCOMP_RET_LOG+ audit — Filter'a bir defaultLOGfallback koyup/proc/sys/kernel/seccomp/actions_loggediçindelog'u enable et; beklenmeyen syscall number'ları auditdSECCOMPrecord'larında (type=SECCOMP) görünür. Bu, coverage gap'lerini production'da pasif olarak ortaya çıkarır.- Architecture anomalisi — auditd/telemetry'de
arch=alanının beklenenden farklı olması (native process'teAUDIT_ARCH_I386ya da__X32_SYSCALL_BITset number'lar) bir ABI-confusion denemesine işaret eder. SECCOMP_RET_KILL_*kill event'leri — Sürekli kill loglayan bir process, denylist'in dışına taşmaya çalışan kodu ele verir;dmesg/audit'teseccompkill satırlarını izle.SECCOMP_RET_USER_NOTIFsupervisor logları — notify tabanlı sandbox'ta supervisor, her denetlenen syscall'ı veSECCOMP_IOCTL_NOTIF_ID_VALIDbaşarısızlıklarını loglayabilir; ID-invalid oranındaki artış TOCTOU/target-abandon denemelerinin sinyalidir.
Mitigation¶
- Allowlist, denylist değil — Default action'ı
SECCOMP_RET_KILL_PROCESS(ya daERRNO) yap, yalnızca gerekli syscall'ları allow'la. Böylece yeni/unutulmuş number'lar otomatik kapalıdır. - Her zaman
arch'ı kontrol et — Filter'ın en başındadata->arch'ı beklenen tek değere pin'le ve eşleşmeyen her şeyi kill et; ayrıca__X32_SYSCALL_BITset edilmiş number'ları explicit olarak reddet. Bu, architecture-confusion class'ını tümüyle kapatır. SECCOMP_FILTER_FLAG_TSYNC— Filter'ıseccomp()ile bu flag'i vererek yükle; process'in tüm thread'leri aynı filter tree'ye senkronlanır, böylece bir thread filtresiz kalıp bypass sağlamaz. Sync başarısız olursa çağrı offending thread ID'sini döndürür.- Pointer'ı native filter'da denetlemeye çalışma — Pointed-to content'e göre karar gerekiyorsa
SECCOMP_FILTER_FLAG_NEW_LISTENERile seccomp user-notification (SECCOMP_RET_USER_NOTIF) kullan. Supervisor, target belleğini/proc/<pid>/memüzerinden okurken TOCTOU'yu önlemek için okumadan önce ve sonraSECCOMP_IOCTL_NOTIF_ID_VALIDçağırmalıdır; iki check arasında değer değişir ya da target syscall'ı signal ile abandon ederse veri invalid sayılır. - Argument-copy pattern — Kernel'in
openat2()/clone3()gibi extensible-argument syscall'larda benimsediği yaklaşım: top-level struct'ı kernel kopyalayıpseccomp_data'ya sunar, böylece filter unsafe pointer chasing yapmadan gerçek flag'ler üzerinden karar verir. - Güncel kernel kullan: pre-5.4 kernel'ler 512–547 aralığındaki x32 number'larını hatalı ele alıyordu; güncel kernel'de
__X32_SYSCALL_BITsemantiği düzeltilmiştir.