linux可以動態的加載內核模塊,在很多場合可能需要確保加載內核的安全性。如果被攻擊者加載惡意內核模塊,將會使得內核變得極其危險。
當然,穩妥的做法就是給內核模塊進行簽名,內核只加載能正確驗證的簽名。這是最首先想到的方法,當然,這個方法並不是很簡單,你需要選用一套公鑰加密方法,一般就是rsa算法了。難點是要在內核中進行驗證模塊的簽名,這需要修改內核中的一些對應的地方。明顯在load_moduler處需要添加簽名驗證邏輯。為此還需要在內核里實現rsa算法,rsa算法實現的難點就是大數的支持,可能需要一直類似於mpi這樣的庫。另外還需要在內核中解析公鑰文件,以及與應用層的公鑰文件交互問題。這些實現起來,工作量確實不小,但令人欣慰的是,linux在3.7版本的內核里都已經實現了這些。其實這些工作以前是以補丁的方式存在的,參見http://lwn.net/Articles/470435/,稱為linux-modsign,終於在3.7的內核里修成正果了.呵呵
其實,看到3.7內核里實現了這些,並不能引起某些人的欣慰.因為自己使用的或者公司使用的內核版本沒有這么高,估計都是在2.6的樣子.你可能想着將3.7的機制移植到2.6上去.我想這工作量也足夠大的讓人心出膽怯.內核移植,搞不好造成不穩定,容易引起很大的麻煩.從http://lwn.net/Articles/470435/ 上的給出的git地址 http://git.kernel.org/cgit/linux/kernel/git/dhowells/linux-modsign.git/ 的歷史中,可以看出工作量還是挺大的 . 況且內核為了提供通用性和擴展性,使用的都是一些小框架,還實現了體系結構相關以及對加密硬件模塊的支持,這些都會讓源代碼難以閱讀和理解。如果是我的話,我更寧願冒着升級內核的風險,而不是去嘗試移植代碼。當然熟悉內核的人,完全可以跟蹤linux-modsign去給自己的系統打補丁,或者根據原理實現一個簡化的版本[參見論文 http://www.ee.ryerson.ca/~courses/coe518/LinuxJournal/elj2004-117-signedmodules.pdf].(畢竟內核主線實現的版本還是太復雜了,個人短時間內無法搞定)
鑒於內核改動的風險比較高,可能很多人更傾向於把問題嘗試在用戶層來解決.這個思想是好的,因為避開了風險高的途徑.關鍵問題是,能否在用戶空間來解決這個問題呢.結果是否定的.完全的依賴用戶層來解決內核模塊的安全問題是不現實的,或者只能解決一部分的問題而已.要想達到上述的在內核中給內核模塊進行簽名驗證的同級別安全問題,是不可能的.在root權限下,用戶空間的安全壁壘會被完全攻破.這估計也是linux內核主線使用簽名驗證機制的一個主要原因.確實這是一個真理:沒有內核的支持,操作系統無法在用戶層保障安全的問題。
但用戶層空間的路走死了嘛?其實不然,我們還可以另辟蹊徑。
在內核模塊的支持下,可以在用戶層進行內核模塊的簽名驗證工作。
首先需要實現一個lsm模塊(在高本版內核里,例如2.6.32,內核不再導出register_security類的符號,可能動態 的加載lsm模塊需要重新編譯內核,讓內核導出相關的符號,關於lsm的使用,大家可以查閱相關資料,這里不再介紹),該模塊的作用主要是保護insmod不被篡改,保護模塊自身不被篡改以及相關的啟動腳本不被篡改。lsm做到這些並不復雜,可以將他們放在一個特定目錄下,讓lsm阻止任何對該目錄的寫操作。
內核模塊使用工具insmod或者modprobe(屬於modutils提供系統程序)來加載內核模塊,modprobe最后還是會調用insmod來加載。insmod最終會調用init_module系統調用來加載模塊。
如果修改insmod,在調用init_module系統調用之前進行簽名驗證邏輯的處理,那么這個前提是insmod的安全性要得到保證,insmod不能被攻擊者篡改。
在lsm模塊被加載前,可能運行的是被污染的insmod程序,當然它可以加載惡意的內核模塊。因此,應該在系統啟動后,首先加載lsm模塊,然后加載其他的模塊。這只是理論上的,實際上,這些過程都很快,攻擊者是無法在這些模塊加載之間實施攻擊的(因為網絡服務什么的都還沒開啟)。
因此,問題被轉移到保障lsm安全模塊的順利加載。當然該模塊可能是使用insmod加載的。(如果是被編譯到內核,隨內核啟動而啟動的,這個問題就得到保障了)。所以,insmod是不能被替換的,以及lsm模塊本身也不能被替換。
有三個關鍵點:
1. insmod不會被替換;
2. lsm模塊必須被正確加載。
3. lsm模塊不能被卸載。(卸載意味着安全機制不再起作用了)
問題1和2解決后,能達到在root權限沒有被攻擊者獲取額前提下,實施對內核模塊的保護。Root權限會卸載你的lsm模塊,然后修改insmod,然后加載惡意模塊....不過攻擊者不能持續保留這種攻擊行為,下次啟動后,以前加載的惡意模塊便不會被加載。
從上面的論述看來,問題1和2的解決是可以的:
假設一個前提:系統第一次啟動是絕對安全的。
在這個前提下,在lsm模塊里阻止任何對insmod和lsm模塊本身的修改操作(利用lsm的hook點實現這些很輕松,實際情況里還需要保護一些加載lsm模塊之類的敏感的腳本等),即使root權限也不行。這樣就解決了這兩個問題。第一次啟動是安全的,也就意味着lsm模塊被正確加載,這樣保護機制就建立起來了。因為假設的前提是可以保證的,因此原理上是可行的。這個問題看上去像是insmod保護內核模塊,而lsm模塊又反過來保護insmod和lsm模塊字節,相互幫助實現安全。
對於問題3,確實很棘手。如果不解決,那么系統的安全級別還是不夠高。其實最容易想到的方法就是把lsm模塊直接編譯進內核,這樣就一起解決了問題2和問題3.不過,又要改內核了,不過這里的改動相對在內核里改load_module等相關聯代碼而言,要簡單的多。
在2.6的內核里,添加了CONFIG_MODULE_UNLOAD標記,用於禁止卸載內核模塊的,參見網址http://www.ibm.com/developerworks/cn/linux/l-cn-kernelmodules/。
其實還可以通過編程的技巧來決絕這個問題,不注冊模塊的module_exit函數,將會導致模塊無法被卸載。
看一下一個hello內核模塊代碼:
1 #include <linux/module.h> 2 #include <linux/kernel.h> 3 #include <linux/init.h> 4 5 static int __init lkp_init(void) 6 { 7 printk("<1>hello world! form the kernel sapce ...\n"); 8 return 0; 9 } 10 11 static void __exit lkp_cleanup(void) 12 { 13 printk("<a>goodbye ,world,leaving kernel sapce ...\n"); 14 } 15 16 module_init(lkp_init); 17 //module_exit(lkp_cleanup); 18 MODULE_LICENSE("GPL");
這里我故意注釋掉了module_exit(lkp_cheanup),模塊被編譯后正常加載到內核中。
使用lsmod可以看到信息:
[root@localhost hello]# lsmod Module Size Used by hello 501 0 [permanent] 8021q 20355 0 garp 5703 1 8021q stp 1626 1 garp llc 4258 2 garp,stp …
可以看到,hello模塊被標記為permanent,意味着模塊被永久加載,也就是不能卸載,下面使用rmmod測試一下:
root@localhost hello]# rmmod hello.ko ERROR: Removing 'hello': Device or resource busy
居然是不能卸載該模塊,呵呵。可見這方法也能達到效果,而且還很簡單。
上述代碼測試環境:
Cenos 6.4 [root@localhost hello]# uname -a Linux localhost.localdomain 2.6.32-358.el6.i686 #1 SMP Thu Feb 21 21:50:49 UTC 2013 i686 i686 i386 GNU/Linux
由上可見,在內核模塊的基礎上,配合應用程序也可以實現內核模塊的安全加載。
本文簡要介紹了linux內核模塊安全的問題,隨着內核3.7對於內核模塊簽名驗證的支持,這個問題后面越來越變得簡單啦。不過不過對於很多老的版本的內核也可以使用一些其他的方法來達到同樣的功能。如果讀者有什么更好的方法或者建議,渴望被指導!