教你使用eBPF LSM熱修復(fù)Linux內(nèi)核漏洞
本文就是一個(gè)簡(jiǎn)單的eBPF LSM實(shí)現(xiàn)思路,核心內(nèi)容是確定精準(zhǔn)HOOK點(diǎn)的思路。怎么找HOOK點(diǎn)?HOOK點(diǎn)掛載后,對(duì)性能影響是什么?如何做權(quán)衡?接下來(lái),我們一起了解一下。
前言
Linux Security Modules^[2]^ (LSM)是一個(gè)鉤子的基于框架,用于在Linux內(nèi)核中實(shí)現(xiàn)安全策略和強(qiáng)制訪問(wèn)控制。直到現(xiàn)在,能夠?qū)崿F(xiàn)實(shí)施安全策略目標(biāo)的方式只有兩種選擇,配置現(xiàn)有的LSM模塊(如AppArmor、SELinux),或編寫(xiě)自定義內(nèi)核模塊。
Linux Kernel 5.7[^3^ ]引入了第三種方式:LSM擴(kuò)展伯克利包過(guò)濾器^[4]^ (eBPF)(簡(jiǎn)稱BPF LSM)。LSM BPF允許開(kāi)發(fā)人員編寫(xiě)自定義策略,而無(wú)需配置或加載內(nèi)核模塊。LSM BPF程序在加載時(shí)被驗(yàn)證,然后在調(diào)用路徑中,到達(dá)LSM鉤子時(shí)被執(zhí)行。
實(shí)踐出真知
命名空間命名空間
現(xiàn)代操作系統(tǒng)提供了允許對(duì)內(nèi)核資源進(jìn)行的工具。例如FreeBSD有,Solaris有。Linux不一樣,提供了一組看似獨(dú)立的工具,每個(gè)進(jìn)程都允許隔離特定的資源。他就是,經(jīng)過(guò)多年來(lái)不停迭代,孕育了、、應(yīng)用。大部分是沒(méi)有爭(zhēng)議的,如UTS命名空間,它允許主機(jī)系統(tǒng)隱藏主機(jī)名和時(shí)間。其他的則比較復(fù)雜但簡(jiǎn)單明了————眾所周知,NET和NS(mount)命名空間很難讓人理解。最后,還有一個(gè)非常特殊、非常有趣的。partitioning``jails``zones``Namespaces``Docker``lxc``firejail``Namespaces``USER Namespaces
USER Namespaces很特別,因?yàn)樗试S所有者作為操作。其工作原理超出了本文的范圍,但是,可以說(shuō)它是讓等工具不作為真正的root操作,或者說(shuō)是容器。root``Docker``rootless
由于其特性,允許未授權(quán)用戶訪問(wèn)總是會(huì)帶來(lái)很大的安全風(fēng)險(xiǎn)。其中最大的風(fēng)險(xiǎn)是。USER Namespaces``提權(quán)
提權(quán)原理
提權(quán)是操作系統(tǒng)的常見(jiàn)攻擊面。user獲得權(quán)限的一種方法是通過(guò)unsharesyscall[5]將其命名空間映射到空間,并指定標(biāo)志。這會(huì)告訴創(chuàng)建一個(gè)具有完全權(quán)限的新用戶命名空間,并將新用戶和Group ID映射到以前的命名空間。即使用unshare(1)[6]程序?qū)oot映射到原始命名空間:root``CLONE_NEWUSER``unshare
多數(shù)情況下,使用是沒(méi)有風(fēng)險(xiǎn)的,都是以較低的權(quán)限運(yùn)行。但是,已經(jīng)被用于提權(quán)了,比如CVE-2022-0492[7],那么本文就重點(diǎn)以這個(gè)場(chǎng)景為例。unshare
Syscalls clone和也很值得研究,都有的功能。但在這篇文章中,我們將重點(diǎn)關(guān)注。clone3``CLONE_NEWUSER``unshare
Debian用add sysctl to disallow unprivileged CLONE_NEWUSER by default^[8]^ 補(bǔ)丁解決了這個(gè)問(wèn)題,但它沒(méi)有被合并到源碼mainline主線中。另一個(gè)類似的補(bǔ)丁sysctl: allow CLONE_NEWUSER to be disabled嘗試合并到mainline,但被拒絕了。理由是在某些特定應(yīng)用中無(wú)法切換到該特性^[9]^ 。在控制對(duì)用戶命名空間的訪問(wèn)[^10]^ 一文中,作者寫(xiě)道:
...目前的補(bǔ)丁似乎沒(méi)有一條通往mainline主線的捷徑。
如你所示,補(bǔ)丁最終沒(méi)有包含到vanilla內(nèi)核^[11]^ 中。
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個(gè)人覺(jué)得比較好的學(xué)習(xí)書(shū)籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書(shū)、實(shí)戰(zhàn)項(xiàng)目及代碼)? ?


我們的解決方案LSM BPF
基于上面一些經(jīng)驗(yàn),可以看到限制的代碼似乎行不通,我們決定使用來(lái)規(guī)避這些問(wèn)題。并且不需要修改內(nèi)核,還可以自定義檢測(cè)防御的規(guī)則。USER Namespaces``LSM BPF
尋找合適的候選鉤子
首先,讓我們跟蹤我們的目標(biāo)系統(tǒng)調(diào)用。我們可以在include/linux/syscalls.h^[12]^ 文件中找到原型。
很清晰得看到,在kernel/fork.c^[13]^ 文件中,注釋部分中留下了下一個(gè)位置的線索。在ksys_unshare()^[14]^ 那里調(diào)用。深入研究該函數(shù),發(fā)現(xiàn)了一個(gè)對(duì)unshare_userns()^[15]^ 的調(diào)用。這讓我看到了希望。
現(xiàn)在,我們已經(jīng)確定了syscall實(shí)現(xiàn),但是接下來(lái)的問(wèn)題是用哪些鉤子?怎么選擇合適的鉤子?
從man-pages[16]中了解到unshare用于改變,那么,我們重點(diǎn)關(guān)注include/linux/lsm_hooks.h[17]中的關(guān)于的鉤子。在函數(shù)unshare_userns()[18]中,可以找到對(duì)prepare_creds()[19]的調(diào)用。對(duì)于cred_prepare[20]的HOOK來(lái)說(shuō)看上去不錯(cuò)。為了驗(yàn)證對(duì)prepare_creds()[21]的理解是否正確,接下來(lái)繼續(xù)分析security_prepare_creds()[22]的調(diào)用,可以確認(rèn),其最終會(huì)調(diào)用這個(gè)HOOK:task``task
暫不過(guò)多討論這個(gè)問(wèn)題,現(xiàn)在能確認(rèn)的是這個(gè)HOOK比較合適,因?yàn)檎迷谥械闹氨徽{(diào)用,而unshare_userns()[23]是我們?cè)噲D阻止的操作。prepare_creds()``unshare_userns()``create_user_ns()
LSM BPF解決方案
我們將使用eBPF編譯一次到處運(yùn)行(CO-RE)^[24]^ 的方法對(duì)代碼進(jìn)行編譯。在不同版本內(nèi)核的IDC中,會(huì)特別適用。(不過(guò),國(guó)內(nèi)外大部分五至十年的互聯(lián)網(wǎng)公司,都有著大量低于5.0的內(nèi)核版本)。本文的演示,將只對(duì)x86_64 CPU架構(gòu)系統(tǒng)驗(yàn)證。ARM64的LSM BPF仍在開(kāi)發(fā)中。你可以訂閱BPF郵件列表^[25]^ 來(lái)了解最新進(jìn)展。
此解決方案在上進(jìn)行了測(cè)試,配置如下:Kernel Version >=5.15
如果CONFIG_LSM列表中不包含,則需要你自己重新編譯,并開(kāi)啟選項(xiàng).bpf``lsm=bpf
內(nèi)核空間代碼
開(kāi)始看內(nèi)核空間代碼::deny_unshare.bpf.c
共產(chǎn)
接下來(lái),我們以下列方式為CO-RE重新定位建立必要的結(jié)構(gòu)::deny_unshare.bpf.c
用戶空間
加載程序并將其附加到目標(biāo)的鉤子上是用戶空間的功能。有幾種方法可以做到這一點(diǎn):
Cilium ebpf^[26]^ 項(xiàng)目
防銹裝訂^[27]^
ebpf.io[28]項(xiàng)目展示的其他類庫(kù)landscape
這里,我們將使用原生libbpf^[29]^ 。
生成文件
最后,進(jìn)行編譯,這里使用Makefile
結(jié)果
打開(kāi)一個(gè)新終端,運(yùn)行命令
在另一個(gè)終端里,可以看到成功的被阻止了。
這個(gè)策略有個(gè)附加的特性,可以允許傳遞授權(quán)。
在無(wú)特權(quán)場(chǎng)景中,系統(tǒng)調(diào)用會(huì)提前中止。有特權(quán)情況下的性能影響是什么?
性能對(duì)比
我們將使用一行unshare命令來(lái)映射用戶命名空間,并在中執(zhí)行一個(gè)命令來(lái)進(jìn)行測(cè)量:
使用系統(tǒng)調(diào)用unshare enter/exit的CPU周期分辨率,我們將以root用戶身份測(cè)量以下內(nèi)容:
命令在沒(méi)有策略的情況下運(yùn)行
與策略一起運(yùn)行的命令
我們將使用ftrace^[30]^ 記錄測(cè)量結(jié)果:
此時(shí),我們將專門(mén)為unshare啟用對(duì)系統(tǒng)調(diào)用的跟蹤。現(xiàn)在,我們?cè)O(shè)置調(diào)用的來(lái)計(jì)算CPU周期:enter/exit``enter/exit``time-resolution
接下來(lái),我們開(kāi)始評(píng)測(cè)
在新終端里運(yùn)行策略,執(zhí)行下一個(gè)syscall
現(xiàn)在,我們收集到兩種CALLS結(jié)果進(jìn)行對(duì)比
分別是:
unshare-92014 使用了 63294 個(gè)周期。
取消共享-92019 使用了 70138 個(gè)周期。
可以看到二者之間有6,844(~10%)個(gè)周期的差異,還行。
兩次測(cè)量之間有6,844(~10%)個(gè)周期損失。不錯(cuò)嘛!
這些數(shù)字是針對(duì)單個(gè)系統(tǒng)調(diào)用的,代碼調(diào)用的頻率越高,這些數(shù)字就越多。Unshare通常在任務(wù)創(chuàng)建時(shí)調(diào)用,在程序的正常執(zhí)行期間不會(huì)重復(fù)調(diào)用。對(duì)于你的場(chǎng)景,需要仔細(xì)考慮評(píng)估。
結(jié)尾
我們了解了是什么,如何使用unshare將映射到,以及如何通過(guò)在eBPF中實(shí)現(xiàn)程序來(lái)解決真實(shí)場(chǎng)景的問(wèn)題。跟蹤準(zhǔn)確的鉤子不是一件容易的事,需要有豐富的經(jīng)驗(yàn),以及豐富的內(nèi)核代碼經(jīng)驗(yàn)。這些一個(gè)策略代碼是用C語(yǔ)言編寫(xiě)的,所以我們可以根據(jù)因地制宜,不同的問(wèn)題做不同的策略,代碼經(jīng)過(guò)輕微調(diào)整,就可以快速擴(kuò)展,增加其他鉤子點(diǎn)等。最后,我們對(duì)比了這個(gè)LSM程序的性能影響,性能與安全的權(quán)衡,是你需要考慮的問(wèn)題。LSM BPF``user``root
Cannot allocate memory(無(wú)法分配內(nèi)存)不是拒絕權(quán)限的最準(zhǔn)確的描述。我們提出了一個(gè)補(bǔ)丁[31],用于將錯(cuò)誤代碼從掛鉤傳到調(diào)用堆棧。cred_prepare
最后,我們的結(jié)論就是鉤子非常適合實(shí)時(shí)修復(fù)Linux內(nèi)核漏洞.
嵌入式Linux內(nèi)核?發(fā)布于?16?秒前?閱讀?1只看樓主
本文就是一個(gè)簡(jiǎn)單的eBPF LSM實(shí)現(xiàn)思路,核心內(nèi)容是確定精準(zhǔn)HOOK點(diǎn)的思路。怎么找HOOK點(diǎn)?HOOK點(diǎn)掛載后,對(duì)性能影響是什么?如何做權(quán)衡?接下來(lái),我們一起了解一下。
前言
Linux Security Modules^[2]^ (LSM)是一個(gè)鉤子的基于框架,用于在Linux內(nèi)核中實(shí)現(xiàn)安全策略和強(qiáng)制訪問(wèn)控制。直到現(xiàn)在,能夠?qū)崿F(xiàn)實(shí)施安全策略目標(biāo)的方式只有兩種選擇,配置現(xiàn)有的LSM模塊(如AppArmor、SELinux),或編寫(xiě)自定義內(nèi)核模塊。
Linux Kernel 5.7[^3^ ]引入了第三種方式:LSM擴(kuò)展伯克利包過(guò)濾器^[4]^ (eBPF)(簡(jiǎn)稱BPF LSM)。LSM BPF允許開(kāi)發(fā)人員編寫(xiě)自定義策略,而無(wú)需配置或加載內(nèi)核模塊。LSM BPF程序在加載時(shí)被驗(yàn)證,然后在調(diào)用路徑中,到達(dá)LSM鉤子時(shí)被執(zhí)行。
實(shí)踐出真知
命名空間命名空間
現(xiàn)代操作系統(tǒng)提供了允許對(duì)內(nèi)核資源進(jìn)行的工具。例如FreeBSD有,Solaris有。Linux不一樣,提供了一組看似獨(dú)立的工具,每個(gè)進(jìn)程都允許隔離特定的資源。他就是,經(jīng)過(guò)多年來(lái)不停迭代,孕育了、、應(yīng)用。大部分是沒(méi)有爭(zhēng)議的,如UTS命名空間,它允許主機(jī)系統(tǒng)隱藏主機(jī)名和時(shí)間。其他的則比較復(fù)雜但簡(jiǎn)單明了————眾所周知,NET和NS(mount)命名空間很難讓人理解。最后,還有一個(gè)非常特殊、非常有趣的。partitioning``jails``zones``Namespaces``Docker``lxc``firejail``Namespaces``USER Namespaces
USER Namespaces
很特別,因?yàn)樗试S所有者作為操作。其工作原理超出了本文的范圍,但是,可以說(shuō)它是讓等工具不作為真正的root操作,或者說(shuō)是容器。root``Docker``rootless
由于其特性,允許未授權(quán)用戶訪問(wèn)總是會(huì)帶來(lái)很大的安全風(fēng)險(xiǎn)。其中最大的風(fēng)險(xiǎn)是。USER Namespaces``提權(quán)
提權(quán)原理
提權(quán)
是操作系統(tǒng)的常見(jiàn)攻擊面。user獲得權(quán)限的一種方法是通過(guò)unsharesyscall[5]將其命名空間映射到空間,并指定標(biāo)志。這會(huì)告訴創(chuàng)建一個(gè)具有完全權(quán)限的新用戶命名空間,并將新用戶和Group ID映射到以前的命名空間。即使用unshare(1)[6]程序?qū)oot映射到原始命名空間:root``CLONE_NEWUSER``unshare
$?id
uid=1000(fred)?gid=1000(fred)?groups=1000(fred)?…
$?unshare?-rU
#?id
uid=0(root)?gid=0(root)?groups=0(root),65534(nogroup)
#?cat?/proc/self/uid_map
?????????0???????1000??????????1
多數(shù)情況下,使用是沒(méi)有風(fēng)險(xiǎn)的,都是以較低的權(quán)限運(yùn)行。但是,已經(jīng)被用于提權(quán)了,比如CVE-2022-0492[7],那么本文就重點(diǎn)以這個(gè)場(chǎng)景為例。unshare
Syscalls clone
和也很值得研究,都有的功能。但在這篇文章中,我們將重點(diǎn)關(guān)注。clone3``CLONE_NEWUSER``unshare
Debian用add sysctl to disallow unprivileged CLONE_NEWUSER by default^[8]^ 補(bǔ)丁解決了這個(gè)問(wèn)題,但它沒(méi)有被合并到源碼mainline主線中。另一個(gè)類似的補(bǔ)丁sysctl: allow CLONE_NEWUSER to be disabled嘗試合并到mainline,但被拒絕了。理由是在某些特定應(yīng)用中無(wú)法切換到該特性^[9]^ 。在控制對(duì)用戶命名空間的訪問(wèn)[^10]^ 一文中,作者寫(xiě)道:
...目前的補(bǔ)丁似乎沒(méi)有一條通往mainline主線的捷徑。
如你所示,補(bǔ)丁最終沒(méi)有包含到vanilla內(nèi)核^[11]^ 中。
我們的解決方案LSM BPF
基于上面一些經(jīng)驗(yàn),可以看到限制的代碼似乎行不通,我們決定使用來(lái)規(guī)避這些問(wèn)題。并且不需要修改內(nèi)核,還可以自定義檢測(cè)防御的規(guī)則。USER Namespaces``LSM BPF
尋找合適的候選鉤子
首先,讓我們跟蹤我們的目標(biāo)系統(tǒng)調(diào)用。我們可以在include/linux/syscalls.h^[12]^ 文件中找到原型。
/*?kernel/fork.c?*/
很清晰得看到,在kernel/fork.c^[13]^ 文件中,注釋部分中留下了下一個(gè)位置的線索。在ksys_unshare()^[14]^ 那里調(diào)用。深入研究該函數(shù),發(fā)現(xiàn)了一個(gè)對(duì)unshare_userns()^[15]^ 的調(diào)用。這讓我看到了希望。
現(xiàn)在,我們已經(jīng)確定了syscall實(shí)現(xiàn),但是接下來(lái)的問(wèn)題是用哪些鉤子?怎么選擇合適的鉤子?
從man-pages[16]中了解到unshare用于改變,那么,我們重點(diǎn)關(guān)注include/linux/lsm_hooks.h[17]中的關(guān)于的鉤子。在函數(shù)unshare_userns()[18]中,可以找到對(duì)prepare_creds()[19]的調(diào)用。對(duì)于cred_prepare[20]的HOOK來(lái)說(shuō)看上去不錯(cuò)。為了驗(yàn)證對(duì)prepare_creds()[21]的理解是否正確,接下來(lái)繼續(xù)分析security_prepare_creds()[22]的調(diào)用,可以確認(rèn),其最終會(huì)調(diào)用這個(gè)HOOK:task``task
…
rc?=?call_int_hook(cred_prepare,?0,?new,?old,?gfp);
…
暫不過(guò)多討論這個(gè)問(wèn)題,現(xiàn)在能確認(rèn)的是這個(gè)HOOK比較合適,因?yàn)檎迷谥械闹氨徽{(diào)用,而unshare_userns()[23]是我們?cè)噲D阻止的操作。prepare_creds()``unshare_userns()``create_user_ns()
LSM BPF解決方案
我們將使用eBPF編譯一次到處運(yùn)行(CO-RE)^[24]^ 的方法對(duì)代碼進(jìn)行編譯。在不同版本內(nèi)核的IDC中,會(huì)特別適用。(不過(guò),國(guó)內(nèi)外大部分五至十年的互聯(lián)網(wǎng)公司,都有著大量低于5.0的內(nèi)核版本)。本文的演示,將只對(duì)x86_64 CPU架構(gòu)系統(tǒng)驗(yàn)證。ARM64的LSM BPF仍在開(kāi)發(fā)中。你可以訂閱BPF郵件列表^[25]^ 來(lái)了解最新進(jìn)展。
此解決方案在上進(jìn)行了測(cè)試,配置如下:Kernel Version >=5.15
BPF_EVENTS
BPF_JIT
BPF_JIT_ALWAYS_ON
BPF_LSM
BPF_SYSCALL
BPF_UNPRIV_DEFAULT_OFF
DEBUG_INFO_BTF
DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT
DYNAMIC_FTRACE
FUNCTION_TRACER
HAVE_DYNAMIC_FTRACE
如果CONFIG_LSM列表中不包含,則需要你自己重新編譯,并開(kāi)啟選項(xiàng).bpf``lsm=bpf
內(nèi)核空間代碼
開(kāi)始看內(nèi)核空間代碼::deny_unshare.bpf.c
#include?<linux/bpf.h>
#include?<linux/capability.h>
#include?<linux/errno.h>
#include?<linux/sched.h>
#include?<linux/types.h>
#include?<bpf/bpf_tracing.h>
#include?<bpf/bpf_helpers.h>
#include?<bpf/bpf_core_read.h>
#define?X86_64_UNSHARE_SYSCALL?272
#define?UNSHARE_SYSCALL?X86_64_UNSHARE_SYSCALL
共產(chǎn)
接下來(lái),我們以下列方式為CO-RE重新定位建立必要的結(jié)構(gòu)::deny_unshare.bpf.c
…
typedef?unsigned?int?gfp_t;
struct?pt_regs?{
?long?unsigned?int?di;
?long?unsigned?int?orig_ax;
}?__attribute__((preserve_access_index));
typedef?struct?kernel_cap_struct?{
?__u32?cap[_LINUX_CAPABILITY_U32S_3];
}?__attribute__((preserve_access_index))?kernel_cap_t;
struct?cred?{
?kernel_cap_t?cap_effective;
}?__attribute__((preserve_access_index));
struct?task_struct?{
????unsigned?int?flags;
????const?struct?cred?*cred;
}?__attribute__((preserve_access_index));
char?LICENSE[]?SEC("license")?=?"GPL";
…
用戶空間
加載程序并將其附加到目標(biāo)的鉤子上是用戶空間的功能。有幾種方法可以做到這一點(diǎn):
Cilium ebpf^[26]^ 項(xiàng)目
防銹裝訂^[27]^
ebpf.io[28]項(xiàng)目展示的其他類庫(kù)
landscape
這里,我們將使用原生libbpf^[29]^ 。
#include?<bpf/libbpf.h>
#include?<unistd.h>
#include?"deny_unshare.skel.h"
static?int?libbpf_print_fn(enum?libbpf_print_level?level,?const?char?*format,?va_list?args)
{
????return?vfprintf(stderr,?format,?args);
}
int?main(int?argc,?char?*argv[])
{
????struct?deny_unshare_bpf?*skel;
????int?err;
????libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
????libbpf_set_print(libbpf_print_fn);
????//?Loads?and?verifies?the?BPF?program
????skel?=?deny_unshare_bpf__open_and_load();
????if?(!skel)?{
????????fprintf(stderr,?"failed?to?load?and?verify?BPF?skeleton\n");
????????goto?cleanup;
????}
????//?Attaches?the?loaded?BPF?program?to?the?LSM?hook
????err?=?deny_unshare_bpf__attach(skel);
????if?(err)?{
????????fprintf(stderr,?"failed?to?attach?BPF?skeleton\n");
????????goto?cleanup;
????}
????printf("LSM?loaded!?ctrl+c?to?exit.\n");
????//?The?BPF?link?is?not?pinned,?therefore?exiting?will?remove?program
????for?(;;)?{
????????fprintf(stderr,?".");
????????sleep(1);
????}
cleanup:
????deny_unshare_bpf__destroy(skel);
????return?err;
}
生成文件
最后,進(jìn)行編譯,這里使用Makefile
CLANG??=?clang-13
LLVM_STRIP??=?llvm-strip-13
ARCH?:=?x86
INCLUDES?:=?-I/usr/include?-I/usr/include/x86_64-linux-gnu
LIBS_DIR?:=?-L/usr/lib/lib64?-L/usr/lib/x86_64-linux-gnu
LIBS?:=?-lbpf?-lelf
.PHONY:?all?clean?run
all:?deny_unshare.skel.h?deny_unshare.bpf.o?deny_unshare
run:?all
?sudo?./deny_unshare
clean:
?rm?-f?*.o
?rm?-f?deny_unshare.skel.h
#
#?BPF?is?kernel?code.?We?need?to?pass?-D__KERNEL__?to?refer?to?fields?present
#?in?the?kernel?version?of?pt_regs?struct.?uAPI?version?of?pt_regs?(from?ptrace)
#?has?different?field?naming.
#?See:?https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=fd56e0058412fb542db0e9556f425747cf3f8366
#
deny_unshare.bpf.o:?deny_unshare.bpf.c
?$(CLANG)?-g?-O2?-Wall?-target?bpf?-D__KERNEL__?-D__TARGET_ARCH_$(ARCH)?$(INCLUDES)?-c?$<?-o?$@
?$(LLVM_STRIP)?-g?$@?#?Removes?debug?information
deny_unshare.skel.h:?deny_unshare.bpf.o
?sudo?bpftool?gen?skeleton?$<?>?$@
deny_unshare:?deny_unshare.c?deny_unshare.skel.h
?$(CC)?-g?-Wall?-c?$<?-o?$@.o
?$(CC)?-g?-o?$@?$(LIBS_DIR)?$@.o?$(LIBS)
.DELETE_ON_ERROR:
結(jié)果
打開(kāi)一個(gè)新終端,運(yùn)行命令
$?make?run
…
LSM?loaded!?ctrl+c?to?exit.
在另一個(gè)終端里,可以看到成功的被阻止了。
$?unshare?-rU
unshare:?unshare?failed:?Cannot?allocate?memory
$?id
uid=1000(fred)?gid=1000(fred)?groups=1000(fred)?…
這個(gè)策略有個(gè)附加的特性,可以允許傳遞授權(quán)。
$?sudo?unshare?-rU
#?id
uid=0(root)?gid=0(root)?groups=0(root)
在無(wú)特權(quán)場(chǎng)景中,系統(tǒng)調(diào)用會(huì)提前中止。有特權(quán)情況下的性能影響是什么?
性能對(duì)比
我們將使用一行unshare命令來(lái)映射用戶命名空間,并在中執(zhí)行一個(gè)命令來(lái)進(jìn)行測(cè)量:
$?unshare?-frU?--kill-child?--?bash?-c?"exit?0"
使用系統(tǒng)調(diào)用unshare enter/exit的CPU周期分辨率,我們將以root用戶身份測(cè)量以下內(nèi)容:
命令在沒(méi)有策略的情況下運(yùn)行
與策略一起運(yùn)行的命令
我們將使用ftrace^[30]^ 記錄測(cè)量結(jié)果:
$?sudo?su
#?cd?/sys/kernel/debug/tracing
#?echo?1?>?events/syscalls/sys_enter_unshare/enable?;?echo?1?>?events/syscalls/sys_exit_unshare/enable
此時(shí),我們將專門(mén)為unshare啟用對(duì)系統(tǒng)調(diào)用的跟蹤?,F(xiàn)在,我們?cè)O(shè)置調(diào)用的來(lái)計(jì)算CPU周期:enter/exit``enter/exit``time-resolution
#?echo?'x86-tsc'?>?trace_clock?
接下來(lái),我們開(kāi)始評(píng)測(cè)
#?unshare?-frU?--kill-child?--?bash?-c?"exit?0"?&
[1]?92014
在新終端里運(yùn)行策略,執(zhí)行下一個(gè)syscall
#?unshare?-frU?--kill-child?--?bash?-c?"exit?0"?&
[2]?92019
現(xiàn)在,我們收集到兩種CALLS結(jié)果進(jìn)行對(duì)比
# cat trace
# tracer: nop
#
# entries-in-buffer/entries-written: 4/4 ? #P:8
#
# ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?_-----=> irqs-off
# ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? / _----=> need-resched
# ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?| / _---=> hardirq/softirq
# ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?|| / _--=> preempt-depth
# ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?||| / _-=> migrate-disable
# ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?|||| / ? ? delay
# ? ? ? ? ? TASK-PID ? ? CPU# ?||||| ?TIMESTAMP ?FUNCTION
# ? ? ? ? ? ? ?| | ? ? ? ? | ? ||||| ? ? | ? ? ? ? |
? ? ? ? unshare-92014 ? [002] ..... 762950852559027: sys_unshare(unshare_flags: 10000000)
? ? ? ? unshare-92014 ? [002] ..... 762950852622321: sys_unshare -> 0x0
? ? ? ? unshare-92019 ? [007] ..... 762975980681895: sys_unshare(unshare_flags: 10000000)
? ? ? ? unshare-92019 ? [007] ..... 762975980752033: sys_unshare -> 0x0
分別是:
unshare-92014 使用了 63294 個(gè)周期。
取消共享-92019 使用了 70138 個(gè)周期。
可以看到二者之間有6,844(~10%)個(gè)周期的差異,還行。
兩次測(cè)量之間有6,844(~10%)個(gè)周期損失。不錯(cuò)嘛!
這些數(shù)字是針對(duì)單個(gè)系統(tǒng)調(diào)用的,代碼調(diào)用的頻率越高,這些數(shù)字就越多。Unshare通常在任務(wù)創(chuàng)建時(shí)調(diào)用,在程序的正常執(zhí)行期間不會(huì)重復(fù)調(diào)用。對(duì)于你的場(chǎng)景,需要仔細(xì)考慮評(píng)估。
結(jié)尾
我們了解了是什么,如何使用unshare將映射到,以及如何通過(guò)在eBPF中實(shí)現(xiàn)程序來(lái)解決真實(shí)場(chǎng)景的問(wèn)題。跟蹤準(zhǔn)確的鉤子不是一件容易的事,需要有豐富的經(jīng)驗(yàn),以及豐富的內(nèi)核代碼經(jīng)驗(yàn)。這些一個(gè)策略代碼是用C語(yǔ)言編寫(xiě)的,所以我們可以根據(jù)因地制宜,不同的問(wèn)題做不同的策略,代碼經(jīng)過(guò)輕微調(diào)整,就可以快速擴(kuò)展,增加其他鉤子點(diǎn)等。最后,我們對(duì)比了這個(gè)LSM程序的性能影響,性能與安全的權(quán)衡,是你需要考慮的問(wèn)題。LSM BPF``user``root
Cannot allocate memory
(無(wú)法分配內(nèi)存)不是拒絕權(quán)限的最準(zhǔn)確的描述。我們提出了一個(gè)補(bǔ)丁[31],用于將錯(cuò)誤代碼從掛鉤傳到調(diào)用堆棧。cred_prepare
最后,我們的結(jié)論就是鉤子非常適合實(shí)時(shí)修復(fù)Linux內(nèi)核漏洞.
原文作者:Linux內(nèi)核之旅
