Kernel Control-Flow Integrity (kCFI)¶
Linux kernel için compiler-enforced forward-edge CFI: her indirect call, hedeften önce saklanan bir type-id hash'ini kontrol eder, böylece corrupt edilmiş bir function pointer yalnızca eşleşen prototype'a sahip fonksiyonlara ulaşabilir.
Mechanism¶
Neden çalışır
Kernel exploit'lerinin geniş bir sınıfı, saklanmış bir function pointer'ını (bir
ops struct'ı, bir timer callback'i, bir workqueue handler'ı) overwrite eder ve
kernel'in onu çağırmasını bekleyerek kontrolü bir gadget'a ya da commit_creds'e
aktarır. Hardware, pointer boyutunda herhangi bir değerin çağrılmasına izin
verir, yani corrupt edilmiş bir pointer ile arbitrary execution arasında duran
tek şey hiçbir şeydir.
kCFI (CONFIG_CFI_CLANG, Clang'in -fsanitize=kcfi'si) bir forward-edge
invariant'ı kurar: bir indirect call yalnızca prototype'ı call site'ın static
type'ıyla eşleşen bir fonksiyona inebilir. Compiler, her fonksiyonun type
signature'ının bir hash'ini hesaplar ve onu fonksiyonun entry'sinden hemen önce
bir preamble olarak yayar. Her indirect call site'ında, hedefin önündeki
word'ü okuyan ve beklenen type id ile karşılaştıran bir kontrol yayar;
uyuşmazlıkta kernel çağırmak yerine bir CFI failure raporlar. Bu, bir saldırganın
ulaşabilir kümesini "herhangi bir adres"ten "tam olarak doğru prototype'a sahip
fonksiyonlar"a indirir ve tipik "pointer'ı overwrite et → gadget'a atla" adımını
etkisiz hale getirir.
kCFI, eski LTO/jump-table CFI'ın low-level halefidir: -fsanitize=cfi-icall'ın
aksine -flto gerektirmez, function pointer'ları jump-table referanslarına
yeniden yazmaz ve fonksiyon-adres eşitliğini asla bozmaz — kernel'in bağımlı
olduğu özellikler. Yalnızca forward-edge'dir (indirect call'lar); backward
edge'ler (return'ler) bir shadow stack ya da hardware Intel CET indirect branch
tracking gibi ayrı bir mekanizma gerektirir.
Walkthrough¶
1. kCFI'ın compile edildiğini doğrula. Clang ile build edilmiş bir kernel gerektirir:
$ grep CONFIG_CFI_CLANG /boot/config-$(uname -r)
CONFIG_CFI_CLANG=y
$ grep CONFIG_CFI_PERMISSIVE /boot/config-$(uname -r)
# CONFIG_CFI_PERMISSIVE is not set
2. Compiler'ın ne yaydığı. Her indirect-callable fonksiyon, type hash'ini entry'sinden hemen önce taşır; her indirect call onu kontrol eder:
# preamble before the target (conceptual, x86-64):
movl $0x12345678, %eax # type id for this prototype
__cfi_some_handler:
nop ...
some_handler: # real entry
...
# at the indirect call site, before `call *%rax`:
cmpl $0x12345678, -4(%rax) # check word in front of target
jne .Lcfi_fail # mismatch -> __cfi_slowpath / report
call *%rax
3. İhlalde davranış. Bir indirect call uyumsuz-type bir hedefe ulaştığında, kernel çağrılan fonksiyonu ve stack trace'i adlandıran bir warning basar, sonra (enforcing modda) bunu fatal bir CFI failure olarak ele alır:
$ dmesg | grep -i cfi
[ 12.84] CFI failure at some_caller+0x4e/0x90 (target: bad_handler+0x0/0x40; expected type: 0x12345678)
Type uyumsuzluklarını debug etmek için permissive mod
CONFIG_CFI_PERMISSIVE=y ihlalleri bir panic yerine warning'lere indirir, böylece
meşru prototype uyumsuzlukları bulunup düzeltilebilir. Açıkça production için
değildir — asıl korumayı devre dışı bırakır.
Yalnızca forward edge, ve yalnızca eşleşen prototype'lar için
kCFI return address'leri korumaz, data-only saldırıları durdurmaz ve aynı prototype hash'ini paylaşan iki fonksiyonu ayırt edemez — aynı-type bir hedef bulan bir saldırgan yine ona ulaşır. Ayrıca tüm kernel'in (ve module'lerin) uyumlu bir Clang ile build edilmesini gerektirir.
Detection¶
Çalışan config'te CONFIG_CFI_CLANG=y, Clang ile build edilmiş bir kernel
(cat /proc/version) ve dmesg'deki CFI failure satırları (ki bunlar ayrıca bir
exploit girişiminin ya da gerçek bir type bug'ının sinyalidir) kCFI'ın aktif
olduğunu gösterir.
Mitigation¶
(Residual risk / bypass.) Saldırganlar aynı-prototype call hedeflerine, data-only / non-control saldırılara ya da kCFI'ın görmezden geldiği backward-edge (return) corruption'a yönelir. Hardware iyileştirmeleri boşluğu daraltır: fineibt, kCFI'ı Intel CET endbranch landing pad'leriyle birleştirir ve bir shadow call stack / CET shadow stack'leri return edge'ini kapsar. kCFI, LTO jump-table şemasının clang-cfi yerini alır.
References¶
- Android Open Source Project. Control flow integrity in the kernel (KCFI). — https://source.android.com/docs/security/test/kcfi
- Clang documentation. Control Flow Integrity (
-fsanitize=kcfi,-fsanitize=cfi-icall). — https://clang.llvm.org/docs/ControlFlowIntegrity.html - LWN.net. Control-flow integrity for the kernel. — https://lwn.net/Articles/810077/