Linux版本:v6.0
處理器架構:ARMv8
前言
學作業系統其實很早就聽過有KASLR(Kernel Address Space Layout Randomization)這個東西了,聽起來就很酷,不過因為程度尚淺,再加上KASLR這種安全相關主題在學作業系統時不算是早期就會接觸到的核心功能,例如虛擬記憶體,檔案系統,CPU排程等等,所以一直沒有機會研究其實作機制。之前有空的時候有稍微深入google一些網路上的相關文章,可惜的是大多數KASLR相關資源只有概念上的解釋,或者非常簡單的帶過,缺的就是一個深入說明實作的文章。
最近剛好有點時間,就趁機trace了這個我一直都很好奇的機制,會以兩篇文章進行簡單的總結,第一篇是內核映像位址的隨機化,第二篇是線性位址的隨機化,希望有所幫助。
若沒有arm64 Linux kernel開機流程的概念,這篇應該會很難看懂
KASLR實作機制
KASLR實作和ARM64 Linux kernel開機流程有緊密的關係,不過這篇目的是介紹KASLR而不是開機流程,所以會先說明執行環境,再從開機非常早期的__primary_switch
開始用程式講解。
執行環境說明
開機到__primary_switch
時MMU尚未開啟,CPU各個重要的系統暫存器已經初始化完成,identity map的頁表也已經初始化,x22
存著FDT (Flat Device Tree)的位址。
__primary_switch
__primary_switch
的意思是”primary CPU switch on MMU”
1 | SYM_FUNC_START_LOCAL(__primary_switch) |
kaslr_early_init
kaslr_early_init
所在的檔案arch/arm64/kernel/pi/kaslr_early.c
會因為Makefile的操作而使得所有裡面定義的符號都多上一個__pi_
前綴,所以上面使用bl __pi_kaslr_early_init
呼叫。
1 | asmlinkage u64 kaslr_early_init(void *fdt) |
__relocate_kernel
去翻__relocate_kernel
實際的程式,會發現我把CONFIG_RELR
的部份拿掉了,這是因為我沒有研究那塊XD,所以無法和大家講解,不過不影響分析,而defconfig
也沒有用這個設定所以目前先跳過
這個函式是KASLR非常重要的一個函式,它負責runtime relocation(執行時重定位),Linux Kernel 編譯過程會在每個存取全局位址的地方產生一個relocation entry,而工具鏈在生成有KASLR功能的內核時,因為不知道最後執行的位址,所以必須要為直接使用全局位址的地方都生成一個relocation entry,讓動態鏈接器(kernel沒有,所以就是kernel自己)能夠在執行時,知道運行時位址之後進行重定位。 這些relocation entries會被集中起來成為kernel的一部分,__relocate_kernel
會iterate過所有的entries並且進行重定位。
這個reference 記載了ARM relocation entry的各種格式,這邊用到的只有一種。
ELF relocation entry的格式如下:
1 | typedef struct { |
KASLR只處理一種重定位,種類是R_AARCH64_RELATIVE
,這個種類的重定位處理方式為把r_offset
這個編譯虛擬位址對應的運行虛擬位址指向的地方改成:”r_addend
加上隨機偏移(也就是運行位址和編譯位址的差)”。這樣講可能很難理解,我們用一個例子說明,今天某個指令想要把sym
這個符號的位址load進暫存器x0
,那麼假設編譯結果是ldr x0, #a0
,這裡#a0
只是舉例,假設sym
這個符號在編譯時決定的位址是0x500
,假設ldr x0, #a0
指令的位址是0x1000
,這個case產生的relocation entry就是r_offset = 0x10a0
,r_addend = 0x500
。再假設這個指令運行時位址是0x3000
(所以sym
運行位址是0x2500
) ,和編譯時差(偏移)0x2000
,我們需要做的事情就是讀出r_addend (0x500)
,加上0x2000
(運行和編譯的偏移),然後存到r_offset
+ (運行和編譯的偏移),也就是把0x2500
存到0x10a0 + 0x2000 = 0x30a0
這個位址。真正運行時x0
經過ldr
就會存著0x2500
,也就是運行時sym
的位址。
1 | SYM_FUNC_START_LOCAL(__relocate_kernel) |
__primary_switch
末尾
bl __relocate_kernel
結束之後馬上就出現了一個全局位址的讀取:
1 | // 這個讀取會產生一個relocation entry,而就在__relocate_kernel被重定位, |
鏈接選項
ARM64 Makefile 中有特別針對KASLR的鏈接選項:
1 | ifeq ($(CONFIG_RELOCATABLE), y) |
有四個選項:
—no-apply-dynamic-relocs
似乎和跳過的CONFIG_RELR
有關,不確定-shared
代表產生一個shared library-Bsymbolic
代表綁定這個shared library裡面所有的全局變數引用到這個shared library本身,目的應該是因為kernel不會有外部引用,所以不要讓ld
產生外部引用的relocation entry-z notext
讀了ld
的man page還是不大確定
一些疑問
雖然經過上面的分析可以了解KASLR的運作方式,但也引出了不少問題,希望之後繼續累積能夠回答它們吧
上面四個鏈接選項實際意義是什麼?加上與否的差別是什麼?
我原本以為關閉KASLR以後所有的relocation entry都會消失,但經過測試他們依然存在,這雖然不影響非KASLR kernel的運作但難道不會浪費空間嗎?
Linux kernel如何只讓工具鏈只生成一種
R_AARCH64_RELATIVE
的relocation type?哪種情況會出現其他種類?