Skip to content

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 da execve() 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 nrarch kontrol 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_64 altında __X32_SYSCALL_BIT set 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'ı ALLOW bı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 default LOG fallback koyup /proc/sys/kernel/seccomp/actions_logged içinde log'u enable et; beklenmeyen syscall number'ları auditd SECCOMP record'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'te AUDIT_ARCH_I386 ya da __X32_SYSCALL_BIT set 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'te seccomp kill satırlarını izle.
  • SECCOMP_RET_USER_NOTIF supervisor logları — notify tabanlı sandbox'ta supervisor, her denetlenen syscall'ı ve SECCOMP_IOCTL_NOTIF_ID_VALID baş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 da ERRNO) 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şında data->arch'ı beklenen tek değere pin'le ve eşleşmeyen her şeyi kill et; ayrıca __X32_SYSCALL_BIT set 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_LISTENER ile 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 sonra SECCOMP_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ıp seccomp_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_BIT semantiği düzeltilmiştir.

References