Linux Kernel CFI機制簡介及測試禁用
PS:要轉載請注明出處,本人版權所有。
PS: 這個只是基于《我自己》的理解,
如果和你的原則及想法相沖突,請諒解,勿噴。
環境說明
??無
前言
??當我們為android移植linux的驅動程序的時候,總會遇到一些錯誤,這些錯誤有一部分就是android 內核開啟的安全的機制導致的。本文就會介紹一種內核的安全機制:Kernel Control Flow Integrity(kCFI)。
??此外,這里還要說明一下,Control Flow Integrity(CFI)與 Kernel Control Flow Integrity(kCFI)是不一樣的,kCFI只檢查函數指針,CFI還具備其他很多的檢查,詳情請參考:https://clang.llvm.org/docs/ControlFlowIntegrity.html 。
Kernel Control Flow Integrity(kCFI)原理簡單介紹
??Control Flow Integrity的翻譯是控制流完整性,從直譯來看,其實就是用一些方法保證來保證我們的指令執行到正確的位置。我們從clang官方文檔知道,kCFI只檢查函數指針,那么其實kCFI就是保證函數指針跳轉到正確的位置,并且返回到正確的位置。
??從這里來看,其實我們可以看到對于函數指針來說,我們需要保護兩個地方:跳轉到正確的位置、返回到正確的位置。這兩個地方有兩個專有名詞:
- forward-edge
- backward-edge
??此外,我們還應該知道,在編寫代碼的時候,分為直接函數調用(direct function call),間接函數調用(indirect function call)。他們的示例如下:
void target(void)
{
//... ...
}
typedef void(*fn)(void);
int main(int argc, char * argv[])
{
// direct function call
target();
//indirect function call
fn _id_fn = target;
_id_fn();
}
??從示例可以知道,indirect function call其實就是函數指針這種調用形式。
??此外,我們還要知道,如果我們想破壞代碼的執行流,那么我們必須在可寫、可讀、可執行的內存里面寫入shellcode,并跳轉到這個shellcode,否則我們的代碼是無法工作的。那么顯而易見的事情是,通過函數指針來調用函數,我們的目標是明確的,因此我們可以校驗這些目標的原型、地址等等信息。
??因為我們需要驗證目標的原型、地址等等信息,所以,當我們在生成可執行文件的時候,需要知道所有的函數目標的信息,這個時候,就需要一個叫做Link Time Optimization(LTO)的功能,因為只有最終可執行文件鏈接時,才知道所有的函數目標信息。
kCFI演示示例
??首先在qemu中運行一個arm64的linux模擬器,然后為linux內核配置如下內核選項:
# General architecture-dependent options -> LTO
CONFIG_CFI_CLANG=y
CONFIG_CFI_PERMISSIVE=y
??我們的測試驅動例子:
#include <linux/module.h> // 必須的頭文件,定義了MODULE_*宏
#include <linux/kernel.h> // 包含內核信息頭文件
#include <linux/init.h> // 包含 __init 和 __exit 宏
static int param_int = 0;
module_param(param_int, int, 0644);
static void hello_cfi_i(int i){
printk(KERN_INFO "hello_cfi_i\n");
}
static void hello_cfi_f(float i){
printk(KERN_INFO "hello_cfi_f\n");
}
typedef void (*hello_cfi_func_i)(int);
typedef void (*hello_cfi_func_f)(float);
struct node {
hello_cfi_func_i i0[1];
hello_cfi_func_f f0[1];
hello_cfi_func_i i1[1];
hello_cfi_func_f f1[1];
hello_cfi_func_i i2[1];
hello_cfi_func_f f2[1];
};
struct node fn_arr = {
.i0 = {hello_cfi_i},
.f0 = {hello_cfi_f},
.i1 = {hello_cfi_i},
.f1 = {hello_cfi_f},
.i2 = {hello_cfi_i},
.f2 = {hello_cfi_f},
};
// 模塊初始化函數
static int __init hello_init(void)
{
fn_arr.i0[param_int](param_int);
printk(KERN_INFO "Hello, World!\n");
return 0; // 返回0表示加載成功
}
// 模塊清理函數
static void __exit hello_exit(void)
{
printk(KERN_INFO "Goodbye, World!\n");
}
// 注冊模塊初始化和清理函數
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL"); // 模塊許可證
MODULE_AUTHOR("Your Name"); // 模塊作者
MODULE_DESCRIPTION("A simple Hello World Module"); // 模塊描述
??我們傳入參數0,執行fn_arr.i0[0],測試正常跳轉

??我們傳入參數1,執行fn_arr.i0[1],測試傳入參數原型不匹配(本來應該調用hello_cfi_i,實際調用hello_cfi_f)

??測試數組越界訪問

后記
??從上面來看,kCFI一般會對調用類型、調用的目標地址進行判斷,更多細節,去看CFI的具體原理。
參考文獻
- https://clang.llvm.org/docs/ControlFlowIntegrity.html
- https://source.android.com/docs/security/test/kcfi
- https://outflux.net/slides/2020/lca/cfi.pdf

PS: 請尊重原創,不喜勿噴。
PS: 要轉載請注明出處,本人版權所有。
PS: 有問題請留言,看到后我會第一時間回復。
浙公網安備 33010602011771號