更多文章目錄:點擊這裏
GitHub地址:https://github.com/ljrkernel
一、eBPF簡介
eBPF是將原先的BPF發展成一個指令集更復雜、應用範圍更廣的“內核虛擬機”。eBPF支持在用戶態將C語言編寫的一小段“內核代碼”注入到內核中運行,注入時要先用llvm編譯得到使用BPF指令集的elf文件,然後從elf文件中解析出可以注入內核的部分,最後用bpf_load_program
方法完成注入。 用戶態程序和注入到內核中的程序通過共用一個位於內核中map實現通信。爲了防止注入的代碼導致內核崩潰,eBPF會對注入的代碼進行嚴格檢查,拒絕不合格的代碼的注入。
注入程序bpf_load_program()
加入了更復雜的verifier 機制,在運行注入程序之前,先進行一系列的安全檢查,最大限度的保證系統的安全。具體來說,verifier機制會對注入的程序做兩輪檢查:
- 首輪檢查(First pass,實現於
check_cfg()
)可以被認爲是一次深度優先搜索,主要目的是對注入代碼進行一次 DAG(Directed Acyclic Graph,有向無環圖)檢測,以保證其中沒有循環存在,除此之外,一旦在代碼中發現以下特徵,verifier 也會拒絕注入:
- 代碼長度超過上限;
- 存在可能會跳出 eBPF 代碼範圍的 JMP,這主要是爲了防止惡意代碼故意讓程序跑飛;
- 存在永遠無法運行(unreachable)的 eBPF 指令,例如位於 exit 之後的指令;
- 次輪檢查(Second pass,實現於
do_check()
)較之於首輪則要細緻很多:在本輪檢測中注入代碼的所有邏輯分支從頭到尾都會被完全跑上一遍,所有指令的參數(寄存器)、訪問的內存、調用的函數都會被仔細的捋一遍,任何的錯誤都會導致注入程序失敗。
二、eBPF的應用(bcc的安裝和使用)
目前可以用 C 來實現 BPF,但編譯出來的卻仍然是 ELF 文件,無法直接注入內核,bcc實現了一步到位的生成出 BPF 代碼。bcc 是一個 python 庫,其中有很大一部分的實現是基於 C 和 C++的,python 是實現對 bcc 應用層接口的封裝。使用 BCC 進行 BPF 的開發仍然需要我們自行利用 C 來設計 BPF 程序,但編譯、解析 ELF、加載 BPF 代碼塊以及創建 map 等可以由 bcc框架實現。
1、安裝bcc
(1)檢查內核配置選項
在使用bcc之前需要檢查Linux系統內核配置選項,可以通過less
命令查看內核配置選項
/proc/config.gz
或
/boot/config-<kernel-version>
less /boot/config-4.18.0-15-generic
BPF檢查項如下:
CONFIG_BPF=y
CONFIG_BPF_SYSCALL=y
# [optional, for tc filters]
CONFIG_NET_CLS_BPF=m
# [optional, for tc actions]
CONFIG_NET_ACT_BPF=m
CONFIG_BPF_JIT=y
# [for Linux kernel versions 4.1 through 4.6]
CONFIG_HAVE_BPF_JIT=y
# [for Linux kernel versions 4.7 and later]
CONFIG_HAVE_EBPF_JIT=y
# [optional, for kprobes]
CONFIG_BPF_EVENTS=y
bcc 框架檢查項:
CONFIG_NET_SCH_SFQ=m
CONFIG_NET_ACT_POLICE=m
CONFIG_NET_ACT_GACT=m
CONFIG_DUMMY=m
CONFIG_VXLAN=m
(2)設置內核配置選項
若檢查內核配置選項與上述檢查項不符,在/usr/src
目錄下使用 make menuconfig
命令設置內核配置選項:
設置完成後需要編譯內核。
(3)安裝bcc
設置好內核配置選項後,開始安裝bcc,使用如下命令:
第一步,安裝 bcc 軟件包
sudo apt-get install bpfcc-tools linux-headers-$(uname -r)
第二步,Ubuntu下構建依賴關係
sudo apt-get -y install bison build-essential cmake flex git libedit-dev \
libllvm6.0 llvm-6.0-dev libclang-6.0-dev python zlib1g-dev libelf-dev
第三步,安裝並編譯bcc
git clone https://github.com/iovisor/bcc.git
mkdir bcc/build; cd bcc/build
cmake .. -DCMAKE_INSTALL_PREFIX=/usr
make
sudo make install
到此,eBPF & bcc 已經安裝完成。
2、使用bcc
下面介紹一個簡單的例子,代碼如下:
from bcc import BPF
BPF(text='int kprobe__sys_clone(void *ctx) { bpf_trace_printk("Hello, World!\\n"); return 0; }').trace_print()
可以看到,這段代碼是在 python 中內嵌 C 語言程序的,請注意六點:
text='...'
這裏定義了一個內聯的、C 語言寫的 BPF 程序。kprobe__sys_clone()
這是一個通過內核探針(kprobe)進行內核動態跟蹤的快捷方式。如果一個 C 函數名開頭爲 kprobe__ ,則後面的部分實際爲設備的內核函數名,這裏是 sys_clone() 。- void *ctx 這裏的 ctx 實際上有一些參數,不過這裏我們用不到,暫時轉爲 void * 。
bpf_trace_printk()
這是一個簡單的內核工具,用於 printf() 到 trace_pipe(譯者注:可以理解爲 BPF C 代碼中的 printf())。它一般來快速調試一些東西,不過有一些限制:最多有三個參數,一個%s ,並且 trace_pipe 是全局共享的,所以會導致併發程序的輸出衝突,因而 BPF_PERF_OUTPUT() 是一個更棒的方案,我們後面會提到。- return 0 這是一個必須的部分。
trace_print()
一個 bcc 實例會通過這個讀取 trace_pipe 並打印出來。
使用如下命令運行此示例:
sudo python2 hello_world.py
切換終端輸入ls做測試時,運行結果如下圖:
可以看到有新進程被創建,程序打印出“Hello, World!”。