ShadowCallStack¶
Yalnızca return address'leri saklayan, compiler tarafından enstrümante edilmiş, ayrı allocate edilen bir stack; buffer overflow'lardan gelen return-address overwrite'larını yener.
Mechanism¶
Note
ShadowCallStack (SCS), LLVM/Clang'de bir compiler hardening mode'udur
(-fsanitize=shadow-call-stack). Uyguladığı invariant şudur: bir
fonksiyonun return address'i, overflow'ların tipik olarak bozduğu data
buffer'larından farklı ve onlara bitişik olmayan bir memory bölgesinden geri
okunur.
Normal bir frame, kaydedilmiş return address'i local array'lerle aynı bitişik
stack üzerinde saklar. Bu yüzden bir local buffer'ın linear overflow'u return
slot'una ulaşır — kanonik stack-smash. SCS, her call frame başına yalnızca
bir word — return address — tutan ikinci bir stack bulundurarak bu bitişikliği
kırar. Fonksiyon prolog'unda return address shadow stack'e push edilir;
epilog'da ret'ten önce (regular stack'ten değil) shadow stack'ten geri
okunur.
Shadow stack'e, ona işaret eden pointer asla attacker'ın erişebileceği stack
memory'sinde yaşamasın diye özel, ABI-reserved bir register (SCSReg)
üzerinden referans verilir:
- AArch64:
x18 - RISC-V (software):
x3 - RISC-V (hardware, Zicfiss):
-fcf-protection=returnilessp
Shadow stack kendi randomize edilmiş base address'i ile bağımsız olarak
allocate edildiğinden ve ona işaret eden pointer'lar nadir olduğundan, bitişik
bir buffer overrun veya tipik bir use-after-free'nin return address'in shadow
kopyasını da üzerine yazması çok olası değildir. Değer regular stack üzerinde
de kalır, ama yalnızca unwinder'lar için — bunun dışında control flow için
kullanılmaz. Leaf fonksiyonlar doğası gereği güvenlidir çünkü bu target'larda
call/ret stack üzerinde değil bir register (link register) üzerinde
çalışır.
Walkthrough¶
AArch64'te enstrümante edilmiş prolog/epilog şöyle görünür. x18 shadow-stack
pointer'ıdır; return address x30 (LR) ona spill edilir:
str x30, [x18], #8 ; push LR onto shadow stack, post-increment
stp x29, x30, [sp, #-16]! ; normal prologue (saves x29/x30 to real stack)
mov x29, sp
bl bar
add w0, w0, #1
ldp x29, x30, [sp], #16 ; normal epilogue restore (ignored for control)
ldr x30, [x18, #-8]! ; pop LR from shadow stack, pre-decrement
ret ; returns to the *shadow* copy
SCS ile bir program build etmek, hem compile hem link zamanında flag'i, ayrıca AArch64'te register'ı reserve etmeyi gerektirir:
AArch64'te, target ABI zaten x18'i reserve etmiyorsa -ffixed-x18 geçmeniz
gerekir (Android, Darwin, Fuchsia ve Windows onu reserve eder).
Mode'u compile zamanında tespit edip tek tek fonksiyonları dışarıda bırakabilirsiniz:
#if defined(__has_feature)
# if __has_feature(shadow_call_stack)
# define HAS_SCS 1
# endif
#endif
/* exclude a function (e.g. one doing custom stack switching) */
__attribute__((no_sanitize("shadow-call-stack")))
void context_switch(void) { /* ... */ }
Warning
SCS, compiler-rt'de kendi runtime'ını sağlamaz: "A runtime is not
provided in compiler-rt so one must be provided by the compiled application
or the operating system." Android'de runtime, shadow stack'i allocate eder ve
thread teardown için guard-region adresini TLS'te saklar.
-fsanitize=shadow-call-stack'i SCSReg'in allocation/kurulumunu sağlamadan
çıplak bir target'ta enable ederseniz, enstrümante edilmiş prolog
initialize edilmemiş bir register'ı dereference eder.
Detection¶
SCS ile build edilmiş bir binary, non-leaf prolog'larındaki/epilog'larındaki
str x30, [x18], #8 / ldr x30, [x18, #-8]! deyiminden ve x18'in reserve
olmasından (asla scratch register olarak kullanılmamasından) tanınabilir.
Runtime'ı olan sistemlerde, içine seyrek pointer'lar olan, ayrı map'lenmiş,
randomize edilmiş bir bölge shadow stack'in kendisidir.
Mitigation¶
Dokümantasyonda belirtilen bilinen sınırlamalar ve zayıflıklar:
- Side channel'lar / address guessing. Koruma, shadow stack'in adresinin gizli kalmasına bağlıdır. "OS-level and processor side channels"'a ve bölgeyi bulan address-guessing saldırılarına karşı savunmasızdır.
jmp_bufsızıntısı. Shadow stack pointer'ı,setjmp/longjmptarafından kullanılanjmp_bufgibi yapılar üzerinden sızabilir.- Data-only saldırılar. SCS yalnızca return address'i korur. Indirect call'ları veya bozulmuş data/function pointer'larını kısıtlamaz, bu yüzden JOP'u, COOP'u veya data-only chaining'i durdurmaz — bkz. block-oriented-programming ve shadow-stack-aware-rop.
- Register-pressure / ABI kısıtları. SCS kodu ile bir thread'i paylaşan her
kod
SCSReg'i reserve etmelidir, bu da mixed-toolchain deployment'ı sınırlar.
Forward-edge CFI (bkz. clang-cfi) ve hardware shadow stack'ler (intel-cet-shadow-stack, arm-pacbti) ile birbirini tamamlar.