SafeStack¶
LLVM
-fsanitize=safe-stack(from Code-Pointer Integrity, Kuznetsov et al. OSDI'14) splits each thread's stack into a safe stack holding return addresses and a separate unsafe stack holding overflowable buffers, so a stack buffer overflow can never reach a return address.
Mechanism¶
Neden çalışır
Klasik bir stack buffer overflow çalışır çünkü güvenlik açığı olan buffer ve saved
return address aynı bitişik stack frame'de yaşar: yerel bir char buf[N]'in
sonunu aşan bir yazma yukarı doğru, saved return address'in içine walk eder. SafeStack
bu bitişikliği kırar.
Compiler her fonksiyonun yerelleri üzerinde bir escape/safety analizi yapar ve onları bölümlere ayırır:
- safe stack, "her zaman güvenli bir şekilde erişilen" objeleri tutar — return address, register spill'leri ve compiler'ın in-bounds olduğunu kanıtlayabildiği erişim pattern'ine sahip yereller.
- unsafe stack, "geri kalan her şeyi" tutar; başlıca array'ler ve address'i escape eden ya da compiler'ın sınırlandıramayacağı bir şekilde index'lenen herhangi bir yerel.
Bir saldırganın overflow edebileceği tek objeler artık unsafe stack'te yaşadığı ve return address'ler fiziksel olarak ayrı safe stack'te yaşadığı için, bir unsafe buffer'ın overflow'u "safe stack içeriğini corrupt edemez." Invariant, control data'nın saldırgan-yazılabilir data'dan uzamsal ayrımı'dır — bir shadow stack ile aynı fikir, ama return address'i çoğaltarak değil, hangi objelerin nereye gideceğini ayırarak elde edilir. Safe stack'in konumu randomize edilir ve gizlenir: bütünlüğü, saldırganın address'ini bilmemesine (ya da ona bir pointer'ı corrupt edememesine) dayanır.
SafeStack, daha geniş Code-Pointer Integrity tasarımının ucuz backward-edge yarısıdır; tam CPI/CPS forward-edge (indirect call/vtable) koruması ekler.
Walkthrough¶
1. Flag ile compile et. Hem compile hem link'e geçirilmelidir:
2. Runtime'da iki-stack modeli. Geleneksel %rsp safe stack'i göstermeye devam eder.
Unsafe stack pointer'ı bir thread-local değişkende tutulur ve yalnızca gerçekten bir
unsafe frame'e ihtiyaç duyan fonksiyonlar için başvurulur:
Çoğu küçük leaf fonksiyonun unsafe yereli yoktur, bu yüzden unsafe stack'e asla dokunmaz — ölçülen overhead'in "ortalama olarak %0.1'den az" olmasının nedeni budur.
3. Unsafe stack'i programından sorgula belgelenmiş arayüz aracılığıyla:
#include <sanitizer/safestack_interface.h>
void *p = __safestack_get_unsafe_stack_ptr();
void *bot = __safestack_get_unsafe_stack_bottom();
void *top = __safestack_get_unsafe_stack_top();
Ders kitabı overflow'una etkisi
void f(){ char buf[64]; gets(buf); } verildiğinde, buf unsafe stack'e
yerleştirilirken f'in return address'i safe stack'te kalır. buf'ı overflow etmek
bitişik unsafe-stack data'sını ezer ama ret hâlâ corrupt edilmemiş bir saved
address okur — return address'i yerinde overwrite etmeye dayanan naif
stack buffer overflow ⇒
ret2libc zincirini etkisiz kılar.
Detection¶
SafeStack bir build-time özelliğidir, bir runtime alarmı değil: engellenen bir overflow'u
log'lamaz ya da raporlamaz (yalnızca overwrite'ın zararsız belleğe düşmesini sağlar). Bir
binary'nin onunla build edildiğini, SafeStack runtime'ını link'lediğini /
__safestack_unsafe_stack_ptr'a referans verdiğini kontrol ederek doğrulayın, örn. nm
ya da readelf -s ile.
Mitigation¶
(Artık risk / bypass.) SafeStack yalnızca return address'leri ve register spill'leri korur ve yalnızca information hiding ile:
- indirect-call / function-pointer hijacking'e karşı hiçbir şey yapmaz: "bir saldırgan heap'te ya da unsafe stack'te bir function pointer'ı overwrite edebilir ve bir programın arbitrary bir konumu çağırmasına neden olabilir." Forward edge'ler için onu CPI/CPS ya da Clang CFI ile eşleyin.
- Safe stack pointer'ı sızabilir ve hiding garantisini çökertir —
swapcontext, exception unwinding,__builtin_frame_addressya da runtime bug'ları aracılığıyla. Bir kez açığa çıktığında, safe stack diğer herhangi bir bölge gibi doğrudan writable olur, bu yüzden SafeStack, herret'te doğrulayan hardware-enforced bir shadow stack'ten daha zayıftır. - Uyumluluk istisnaları saldırı yüzeyini genişletir: GC her iki stack'i de taramalı,
sigaltstack()signal handler'ları__attribute__((no_sanitize("safe-stack")))'a ihtiyaç duyar veucontext.hdesteklenmez.
References¶
- Clang documentation. SafeStack. — https://clang.llvm.org/docs/SafeStack.html
- Kuznetsov, Szekeres, Payer, Candea, Sekar, Song. Code-Pointer Integrity, OSDI'14. — https://www.usenix.org/conference/osdi14/technical-sessions/presentation/kuznetsov