Skip to content

Intel CET Shadow Stack

Her return address'in korumalı ikinci bir kopyasını tutan hardware shadow stack; böylece normal return address'in bir stack-buffer overwrite'ı RET'te bir control-protection (#CP) fault'u ile yakalanır ve ROP nötralize edilir.

Mechanism

Neden çalışır

Return-oriented programming tek bir invariant'a dayanır: RET'in tükettiği adres, attacker'ın yazabildiği bellekte (normal stack'te) yaşar. CET'in Shadow Stack'i, return address'in ikinci, hardware tarafından yönetilen bir kopyasını sıradan store'ların dokunamadığı ayrı bir page'de tutarak bu invariant'ı kırar.

  • Bir CALL'da, processor return address'i hem normal stack'e hem de shadow stack'e push'lar. RET'te, shadow-stack kopyasını pop'lar ve normal-stack kopyasıyla karşılaştırır. İkisi farklıysa, processor bir control-protection (#CP) fault'u doğurur. (Linux: "processor return address'i hem normal stack'e hem de shadow stack'e push'lar ... ikisi farklıysa, processor bir control-protection fault'u doğurur.")
  • Shadow stack, özel bir "shadow stack" memory type'ına sahip page'lerde yaşar. Normal MOV ile yazılamazlar; onlara yalnızca call/return makinesi ve özel WRSS/INCSSP/RSTORSSP instruction'ları dokunur ve WRSS ayrıca etkinleştirilmelidir (ARCH_SHSTK_WRSS). Yani kaydedilmiş return address'i yeniden yazan bir heap/stack overflow shadow kopyasını el değmemiş bırakır ve uyuşmazlık kontrol transfer olmadan önce tespit edilir.
  • Shadow-stack pointer'ı mimari bir register'dır, SSP. Normal bir operand olarak adlandırılamaz; yazılım onu RDSSP ile okur ve yalnızca kısıtlanmış instruction'larla ayarlar (frame'leri unwind etmek için INCSSP, stack değiştirmek için RSTORSSP/SAVEPREVSSP artı restore token'ları).
  • Etkinleştirme mode başına IA32_U_CET (user) / IA32_S_CET (supervisor) MSR'ları aracılığıyladır; userspace onu arch_prctl(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK) ile açar, CONFIG_X86_USER_SHADOW_STACK ve user_shstk CPU feature'ı ile koşullanır.

Bu not user-mode (ring-3) tarafı açıklar (IA32_U_CET, arch_prctl, glibc opt-in). Aynı invariant'ı ring 0'a taşıyan — CR4.CET, per-privilege IA32_PL0..3_SSP MSR'ları ve SETSSBSY/CLRSSBSY busy-bit token'larıyla yapılandırılan — ayrı supervisor varyantı için bkz. Intel CET Supervisor Shadow Stack.

Aynı hardware mekanizmasının Windows / Microsoft-mode tarafı — PE-header opt-in, Get-ProcessMitigation/UserShadowStack policy tooling ve WER/#CP crash telemetrisi — ayrı bir not'tadır: bkz. CET Hardware-enforced Stack Protection. Bu sayfa ise Linux/glibc (arch_prctl, /proc introspection, SSP/token semantiği) tarafına odaklanır.

Invariant: return hedefi, attacker'ın forge edemeyeceği bellekten alınır. Bozulmuş bir normal-stack return address'i artık RET'i yönlendiremez; yalnızca bir fault üretir.

Walkthrough

1. Hardware + kernel desteğini doğrula. /proc/cpuinfo'daki user_shstk flag'i userspace shadow-stack desteğini gösterir; Kconfig seçeneği X86_USER_SHADOW_STACK'tir ve nousershstk onu boot'ta devre dışı bırakır.

$ grep -o user_shstk /proc/cpuinfo | head -1
user_shstk

2. Bir binary'nin opt-in yaptığını kontrol et. GCC/Clang, -fcf-protection ile derlendiğinde bir GNU-property note emit eder; readelf SHSTK feature'ını gösterir:

$ readelf -n ./app | grep -a SHSTK
      Properties: x86 feature: SHSTK

3. Shadow stack ile başlatılmış bir glibc bunu thread başına raporlar. Kernel, canlı durumu /proc/$PID/status'ta açar:

$ cat /proc/self/status | grep x86_Thread_features
x86_Thread_features:        shstk
x86_Thread_features_locked: shstk

4. Programatik olarak etkinleştir (glibc'nin startup'ının izlediği yol):

#include <asm/prctl.h>
/* feature bits: ARCH_SHSTK_SHSTK, ARCH_SHSTK_WRSS */
arch_prctl(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK);
/* ARCH_SHSTK_LOCK pins the setting; ARCH_SHSTK_STATUS reads it back */

5. Bir ROP girişiminde fault'u gözlemle. Normal stack'teki kaydedilmiş return address'i overwrite et ve fonksiyonun return etmesine izin ver: shadow kopyası hâlâ gerçek caller'ı tutar, ikisi uyuşmaz ve CPU #CP teslim eder. Linux bunu, gadget'a transfer etmek yerine control-protection signal code'lu fatal bir SIGSEGV'e dönüştürür.

Signal-handling detail (why handlers still work)
On a signal, "the old pre-signal state is pushed on the stack. When shadow
stack is enabled, the shadow stack specific state is pushed onto the shadow
stack" — the old SSP is saved in a special token with bit 63 set, then
verified and restored on sigreturn so the handler's own returns stay
protected.

Kapsam ve artık risk

Shadow Stack yalnızca backward edge'leri (return'leri) korur. Forward-edge hijack'leri (bir indirect CALL/JMP hedefini bozma) etkilenmez — bu, Indirect Branch Tracking'in işidir. Tüm-program kapsaması ayrıca her library'nin -fcf-protection ile derlenmesini gerektirir; enforcement strict değilse tek bir non-CET DSO return'leri korumasız bırakabilir.

Mitigation

(Artık risk / hâlâ neler geçer.) Return'ler kapsanır, ama bir return address'i hiç bozmayan saldırılar hayatta kalır: saf JOP/COOP forward-edge chain'leri ve gerçek C++ virtual-call site'larını yeniden kullanan o spesifik counterfeit-object bypass. Anlamlı bir control-flow integrity için shadow stack'i IBT (ve FineIBT gibi daha ince forward-edge CFI) ile eşleştirmek gerekir.

References