博客 / 詳情

返回

Linux內核安全子系統簡介(下)

查看原文

《Linux內核安全子系統簡介(上)》

資源隔離

資源隔離是一個歷史悠久又異常有效的安全手段。

從操作系統的角度來看,它對各個進程的管理實際上就是一個隔離。每個進程都擁有從0開始的連續一大片地址空間可以使用,但實際上在物理地址上,各個進程卻被分割開來。

在Linux系統下,早期比較出名的資源隔離手段是chroot。Linux用户可以創建一個虛擬的根文件系統,在其中部署軟件,再通過chroot命令運行部署在其中的軟件,這些軟件運行的結果只會影響到虛擬文件系統,這樣就可以避免對真實的根文件系統造成影響。

在當下,Linux操作系統中已經提供了namespace(命名空間),它包括cgroup、IPC、network、mount、pid、user與UTS等多個子系統的隔離支持。例如,通過mount namespace即可創建虛擬的文件系統,而通過pid namespace則可以創建虛擬的進程號系統。使用namespace可以隔離進程的運行環境,使得進程互不影響,也不會影響到系統,這樣即使一個進程出現了安全問題,也不會影響到系統與其它進程。

在Linux操作系統中,程序可以調用clone、unshare與setns等系統調用調整當前進程的namespace,系統管理員也可以通過firejail或者unshare命令設置一個進程的namespace。例如,使用firejail運行firefox可以這樣:

firejail firefox

對於運行firefox究竟應該如何進行限制,則可以通過/etc/firejail/firefox.profile來限制,如下:
圖片

可以看到,大部分限制都是針對路徑進行限制的,即firejail為firefox的運行環境創建了一個mount namespace,這樣就可以將firefox與實際的文件系統進行隔離了。

當然,也可以通過firejail的命令行參數設置命名空間隔離,例如設定DNS等。

配額限制

配額限制可以設定一個進程能使用的計算資源,這些計算資源包括處理器、內存、設備、網絡等物理資源,也可能包括進程轉儲(coredump)、棧大小等更抽象的資源。

配額限制分為多個場景:

  • 如果希望限制當前shell下的進程資源,可以使用ulimit命令
  • 如果希望限制某個登錄會話的進程資源,可以使用pam_limits.so,它使用的配置文件一般是/etc/security/limits.conf,它可以限制的資源與ulimit類似
  • 如果希望限制某個守護進程(即systemd服務)的資源,可以使用systemd.resource-control,它是基於cgroups對守護進程進行資源限制的
  • 對於任意進程,可以使用cgroups對其進行資源限制,一般的步驟是:

    1. 在/sys/fs/cgroup具體資源目錄下創建目錄,例如假設想限制某個進程的資源,則可以sudo mkdir /sys/fs/cgroup/memory/mygroup
    2. 為了操作方便,設置目錄的屬主:sudo -R chown raphael:raphael /sys/fs/cgroup/memory/mygroup
    3. 設置進程的最大內存為64M:echo 64m > /sys/fs/cgroup/memory/mygroup/memory.limit_in_bytes
    4. 將某個進程($mypid)加入此cgroup組中:echo $mypid > /sys/fs/cgroup/memory/mygroup/tasks
    5. 如果希望一啓動進程就將其加入某個組,則可以使用cgexec命令

對於當前的雲計算來説,資源隔離與配額限制是最為重要的兩個功能,前者可以使得租户可以互不影響,後者則可以為每個租户配置不同的資源,另外,也可以保證某個進程不至於佔用任意多的資源,從而可以防止拒絕服務的攻擊。

系統沙箱

沙箱這個詞非常模糊,看起來它更像上面説到的資源隔離,就是把各個進程隔離在一個單獨的箱子裏運行。但實際上在Linux中,系統沙箱指的是對系統調用進行過濾的方法,對應的功能是seccomp。

seccomp是Linux提供的一個系統調用,通過seccomp,程序可以限制當前進程在調用系統調用時,可以根據系統調用號、系統調用參數來決定系統調用的返回值。除此之外,也可以使用prctl(PR_SET_SECCOMP...)來提供類似的功能

例如程序可以設置當前進程不能調用socket(即返回錯誤),或者調用socket創建IPv6套接字的時候殺死當前線程,或者調用socket創建IPv4套接字或者Unix域套接字的時候產生一個SIGSYS信號。

這些複雜的邏輯實際上是通過內核提供的cBPF虛擬機實現的。要設置這些邏輯,開發人員需要學習cBPF字節碼,並使用字節碼編程,將字節碼數組通過bpf系統調用傳遞到內核中去。

下面是內核自帶的使用prctl進行系統調用控制的一個例子。在這個例子裏,如果當前進程在x_arch架構下調用系統調用號為x_nr的系統調用時,內核會直接返回x_err的錯誤:
圖片

可以看到對應的BPF字節碼一共有六條(注意所有的跳轉都是相對跳轉),它們的作用依次是:

  • 讀取當前系統的架構至寄存器
  • 寄存器的值與x_arch,若不等則跳到最後一條指令(3),不然繼續執行下一條指令(0)
  • 讀取當前的系統調用號至寄存器
  • 比較寄存器的值與x_nr,若不等則跳到最後一條指令(1),不然繼續執行下一條指令(0)
  • 返回(BPF_RET)錯誤號x_err
  • 返回(BPF_RET)允許繼續進行系統調用(SECCOMP_RET_ALLOW)

cBPF源自於BSD系統,它是基於寄存器的語言,有累加寄存器與索引寄存器兩個32位的寄存器,一共約三十條32位的指令,每條指令包含16位的操作碼(opcode)、8位的為真時跳轉地址,以及8位的為假時跳轉地址,還可以通過JIT進行加速。不過為了保證安全性,cBPF不支持循環。在內部,cBPF會被修改為eBPF進行處理。

使用seccomp最多的就是瀏覽器、容器等軟件。seccomp比較大的問題包括:

需要學習和使用內核的cBPF語言,在用户態沒有特別好的工具鏈可用,內核提供了一個libbpf,但是用起來還是太麻煩,而基於llvm的bcc等工具則需要攜帶整套編譯器,對於生產環境來説非常不便
只能由當前進程調用seccomp限制自身的系統調用,系統管理員無法限制進程
與用户態交互不方便,有時候可能需要讀取用户態管理程序設置的數據判定是否可以進行系統調用

可信計算

可信計算是一套圍繞可信芯片為核心的可信鏈管理體系。其原理是系統出廠時對系統的關鍵部件進行掃描,提取其唯一性的標識(當部件的內容改變時此唯一性標識必須也相應變化),並將其在可信芯片中存儲下來。當系統加電時,會經歷下面幾個步驟:

可信芯片首先對包括處理器、內存、總線在內的硬件部件進行掃描,確認其標識沒有變化之後,再將系統移交給處理器
處理器驗證bootloader的完整性(通過計算bootloader的散列值,並將計算出來的散列值與存儲在可信芯片內的散列值對比),確認其完整性沒有變化之後將系統移交給bootloader
bootloader驗證內核的完整性(同樣是散列值比較),確認其完整性沒有變化之後將其移交給內核
內核驗證外部內核模塊與init相關程序的完整性,若完整性沒有變化之後加載內核模塊,並啓動init程序,之後的完整性校驗則主要被轉交給了用户程序主導

除了上述流程外,可信計算還有幾個安全約束。首先是上述存儲在可信芯片裏的唯一性標識是不可更改的,因此對可信系統的更新是一個棘手的問題。其次是這些唯一性標識也是不可讀的,因此如果需要驗證唯一性,需要將待校驗數據輸入可信芯片,由可信芯片在內部進行散列計算和比對。因此在對大數據量進行完整性校驗時,如果帶寬不足,則可能嚴重影響系統的性能。另外,在實施上,實現完全的可信需要從固件開始做起,因此對整個系統的改造量是比較大的。

除了提供完整性校驗之外,可信芯片還提供了多個加密算法的實現。

可信計算在國外的標準是TPM(trusted platform module),Linux內核早已支持了它,對應內核裏的源碼在drivers/char/tpm目錄下。國內對應的標準是TCM與TPCM,對應的加密算法包括SM2( 橢圓曲線公鑰密碼算法 )、SM3(散列函數)、SM4(分組密碼算法)與SM9(標識密碼算法)等。

在內核裏,除了對TPM的支持外,還有對TPM的應用。對應模塊是IMA與EVM,它們是結合文件擴展屬性對文件完整性進行校驗的,不過其實際應用很少見。

安全審計

在Linux操作系統內核中,審計系統起到的作用是按照設計規則,對整個系統中發生的感興趣的事件進行記錄,並將記錄保存下來成為審計日誌。一般的日誌與審計有相似之處,也有不同之處。相似之處在於它們都可以記錄下系統中發生的事件,不同之處在於日誌一般是基於自覺的行為,應用程序和系統中的守護進程在執行自己任務的過程中會主動自覺的生成日誌,並將其發送給日誌守護進程,由日誌守護進程將其記錄在日誌文件中。而審計則是整個系統內部強制性的事件檢查。

Linux操作系統內核的審計系統由內核態與用户態組成。內核態的審計模塊內嵌於內核之中,屬於不可卸載,可以配置的內核功能模塊。它的主要功能是根據用户態傳入的審計規則,對內核中發生的相應事件進行檢查與審計,並將審計信息傳給用户態守護進程(auditd),由用户態守護進程將其記錄下來。審計系統的用户態程序較多,有負責記錄審計日誌的auditd,有負責提供搜索審計結果的ausearch,有配置審計規則的auditctl,有生成審計報告的aureport,還有可以進行進一步分發的audispd等。

審計系統的內核態與用户態之間的接口使用的是netlink socket,這種通訊方式可以以異步的方式進行回調通信,因此使用較為靈活。auditd守護進程通過netlink套接字持續獲取內核的審計消息,以進行記錄和分發。

內核生成審計信息的途徑主要包括系統調用與主動調用兩種方式,其中主動調用又可以分為內核模塊發起的主動審計信息發生與用户態進程發起的主動審計信息發生。在系統調用的入口和出口,內核會調用kernel/auditsc.c中的__audit_syscall_entry與__audit_syscall_exit函數,分別用來分配與初始化系統調用的審計相關信息(架構、系統調用號、系統調用參數等),以及用來銷燬系統調用的審計相關信息的。

這些函數最終會調用內核的audit_log_開頭的一系列函數,將審計日誌通過netlink套接字發送到auditd進行處理。

其它安全子系統

Linux下的數據透明加密主要可以分為兩類:

  1. 基於塊設備映射技術的透明加密,即基於dev-mapper的dm-crypt等
  2. 基於文件系統的透明加密,如eCryptFS,以及各種加解密的FUSE文件系統

其中Linux下使用最廣的透明加密解決方案LUKS就是依賴於dm-crypt的。

Linux下的網絡防火牆歷經變革,其原理基本都是基於數據包過濾的思想。Linux內核中對應的內核模塊為netfilter/nftables,用户態對應的工具主要是iptables/nft,不過由於eBPF最近幾年的快速發展與應用,所以實際上防火牆已經有相當部分功能可以通過bpf來控制了。

問題與方向

當前Linux內核安全系統設計的問題不少,包括:

使用C/C++開發內核代碼容易導致安全隱患,主要是內存管理的問題,如溢出、泄漏、越界訪問等。雖然有人已經在嘗試使用rust開發內核模塊,但是仍然有很多的問題需要解決
自主訪問控制無法解決豬隊友的問題,強制訪問控制無法解決可擴展性的問題
用户驗證缺乏對新型驗證硬件與技術的支持,以及對圖形用户界面的規範
可信計算難以避免管理僵硬與性能低下的問題

上述問題有的按照現有的方向其實很難解決,因此我們也許需要新的方法來處理。

比如:

對系統與應用進行分離。這一點已經在iOS與Android中取得了成功
形式化驗證。對於小型化系統,例如幾千行或者甚至萬行級別的系統,這也許是可行的
新的訪問控制模型。例如基於對象的能力模型,即oCap
結合證書,而不僅是可信計算的系統閉環管理
新的安全開發語言,例如類似rust的語言,而現在看來lisp/haskell難當重任

附錄:

(1)https://distrowatch.com/index
(2)https://www.deepin.org/zh/kernel-security-subsystem-2/

user avatar jichenssg 頭像 maylet 頭像 er_6097e4c6afae9 頭像
3 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.