Skip to content

Retpoline

Google's "return trampoline": rewrite every indirect call/jmp so the CPU's speculative path is captured in a pause/lfence loop instead of following a poisoned BTB target — defeating Spectre v2 branch target injection.

Mechanism

Neden çalışır

Spectre v2 (branch target injection) çalışır çünkü bir saldırgan Branch Target Buffer (BTB)'yi eğitir ki bir victim'in indirect branch'i (call *%rax, jmp *%rdx) gerçek hedef çözülmeden önce spekülatif olarak saldırgan-seçimli bir gadget'a atlasın.

Bir retpoline, indirect branch'i spekülasyon yolundan tamamen kaldırır. Hile: CPU'nun predict edecek tek kalan branch'i bir ret'tir ve ret, BTB'den değil, Return Stack Buffer (RSB)'den predict edilir. Önce doğrudan bir call ayarlayarak, konstrüksiyon güvenli, iyi huylu bir pause/lfence loop'una işaret eden bir RSB entry'si yerleştirir. Bu arada stack'teki gerçek return address, hakiki hedefle overwrite edilir.

Sonuç bir invariant'tır: mimari olarak ret gerçek hedefe gider; spekülatif olarak RSB, CPU'yu spin loop'una gönderir; orada misprediction çözülene kadar zararsızca spin'ler. Saldırganın zehirlenmiş BTB entry'sine asla başvurulmaz, bu yüzden bir gadget'a ulaşacak bir pencere yoktur.

Walkthrough

1. Klasik thunk. *%r11'e bir indirect branch için, compiler bunun yerine bir trampoline'e doğrudan bir call üretir. Konstrüksiyon (Google'ın açıklamasına göre) şöyledir:

    call set_up_target          ; (1) push return addr -> trains RSB to capture_spec
capture_spec:
    pause                       ; (4) speculation is trapped here, spinning
    jmp capture_spec
set_up_target:
    mov %r11, (%rsp)            ; (2) overwrite return addr with the REAL target
    ret                         ; (3) architecturally returns to *%r11
  • (1) doğrudan call, return address olarak capture_spec'i push eder ve capture_spec'e bir return predict eden bir RSB entry'si oluşturur.
  • (2) gerçek hedef (%r11), stack üzerindeki return address'in üzerine yazılır.
  • (3) ret mimari olarak gerçek hedefe atlar; spekülatif olarak CPU (1)'deki RSB entry'sini tüketir ve (4)'teki pause loop'una düşer.

pause (ve bazı varyantlarda lfence) spekülatif loop'un yürütme kaynaklarını tüketmesini engeller, böylece hassas bir gadget'a yönlendirilemez.

2. Retpoline'lerle bir kernel build etme. Compiler thunk'ları üretir; kernel, hot-patch edilebilsin diye harici bir thunk'a link'lenir:

# GCC
-mindirect-branch=thunk-extern -mindirect-branch-register
# Clang
-mretpoline-external-thunk

# Kconfig
CONFIG_MITIGATION_RETPOLINE=y      # (older trees: CONFIG_RETPOLINE)

3. Runtime'da aktif olduğunu doğrula.

$ cat /sys/devices/system/cpu/vulnerabilities/spectre_v2
Mitigation: Retpolines, IBPB: conditional, RSB filling, ...

Retpoline'ler return'leri korumaz

Retpoline yalnızca indirect call'ları/jump'ları etkisiz kılar. Tüm konstrüksiyon ret'in güvenli olmasına dayanır — ama bazı microarchitecture'larda ret predictor'ının kendisi BTB'den eğitilebilir, ki bu tam olarak Retbleed açığıdır. Bu yüzden retpoline tek başına etkilenen AMD Zen 1/2 ve belirli Intel core'larında yetersizdir ve bir return thunk ile eşlenmelidir.

Mitigation

(Artık risk / bypass.) Retbleed'in ötesinde, RSB underflow edebilir: RSB boşaldığında (derin call stack'leri, context switch'ler, SMM çıkışları) bazı CPU'lar ret prediction'ı için BTB'ye geri döner ve saldırıyı yeniden açar. Kernel bunu, context switch ve VM exit'te RSB stuffing/filling ile karşılar. Daha yeni parçalarda, hardware eIBRS genellikle retpoline'e tercih edilir.

References