Skip to content

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.

$ grep CONFIG_CFI_PERMISSIVE /boot/config-$(uname -r)
CONFIG_CFI_PERMISSIVE=y

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