LWN: BPF可以調(diào)用內(nèi)核函數(shù)了!
關(guān)注了就能看到更多這么棒的文章哦~
Calling kernel functions from BPF
By Jonathan Corbet
May 13, 2021
DeepL assisted translation
https://lwn.net/Articles/856005/
內(nèi)核中的 BPF 虛擬機(jī)可以支持從用戶空間加載程序之后在內(nèi)核的上下文中安全地執(zhí)行。然而,如果不能同內(nèi)核其他部分交互的話,作用就很有限了。BPF 和內(nèi)核之間的接口一直保持在很小的范圍,這其中有很多原因,比如保持安全性以及保持內(nèi)核對(duì)系統(tǒng)的控制。然而,5.13 內(nèi)核包含了一個(gè)功能,今后可以被用來極大地拓寬這些可用的接口:也就是能從 BPF program 直接調(diào)用內(nèi)核函數(shù)。
推動(dòng)這個(gè)功能的最直接原因是要在 BPF 中實(shí)現(xiàn) TCP 擁塞控制算法,這是 Martin KaFai Lau 在 5.6 內(nèi)核中就加入的一項(xiàng)功能。在 BPF 中實(shí)現(xiàn)的擁塞控制代碼中重新實(shí)現(xiàn)了一些內(nèi)核中已經(jīng)存在的函數(shù),這并不是一個(gè)好做法。如果可能的話,最好是能直接使用內(nèi)核中現(xiàn)有的函數(shù)。這次新增的函數(shù)調(diào)用機(jī)制(也是由 Lau 實(shí)現(xiàn)的)就將這種可能性變?yōu)榱爽F(xiàn)實(shí)。
Making functions available to BPF
在 BPF program 中,要使用一個(gè)內(nèi)核函數(shù),現(xiàn)在只需要對(duì)它聲明成 extern 并像其他 C 函數(shù)一樣調(diào)用它即可。而在內(nèi)核里則需要做更多的工作。BPF program 只能訪問一組特定的經(jīng)過允許的函數(shù),而這組函數(shù)只可以供指定的 BPF program type 使用,因此內(nèi)核內(nèi)的代碼必須要確保這些函數(shù)在正確的上下文中可用。舉例來說,這次的代碼提交就是為了讓 tcp_slow_start() 對(duì) BPF 可用,但只有擁塞控制程序才可以使用。
向 BPF 程序 "export" 某些函數(shù)的過程,就是向與 program type 相關(guān)的 bpf_verifier_ops structure 添加一個(gè)新函數(shù):
bool (*check_kfunc_call)(u32 kfunc_btf_id);
當(dāng) BPF verifer 看到一個(gè)對(duì)外部函數(shù)的調(diào)用時(shí),就會(huì)執(zhí)行這個(gè)函數(shù),其中 kfunc_btf_id 是分配給 BPF program 要調(diào)用的函數(shù)的 BPF type format(BTF)ID。如果這個(gè)調(diào)用應(yīng)該被允許,那么此函數(shù)就返回 true。比如說 tcp_slow_start() 是唯一一個(gè)用這種方式提供的函數(shù)的話,那么就可以寫這樣一個(gè)函數(shù):
static bool bpf_tcp_ca_check_kfunc_call(u32 id)
{
return id == BTF_ID(func, tcp_slow_start);
}
如果有很多函數(shù)需要 export 的話,不需要用一長串 if 語句來判斷,還有更簡單的方法,請(qǐng)看相關(guān) commit 中的例子。
除了檢查函數(shù)是否可用之外,BPF verifier 還要進(jìn)行其他一系列檢查。例如檢查傳遞給函數(shù)的參數(shù)及其類型是否正確,不正確的 program 將會(huì)被拒絕。只有當(dāng) verifier 能夠確認(rèn)這個(gè) program 是安全的,才會(huì)允許調(diào)用,盡管 verifier 其實(shí)不能真正知道被調(diào)用的函數(shù)內(nèi)部發(fā)生了什么,也不知道會(huì)出什么錯(cuò)誤。
Some questions
到目前為止,擁塞控制程序是唯一使用了這個(gè)功能的 program type,但不難想象,今后會(huì)有其他 program 出現(xiàn)。這種能力引出了一些有趣的問題,以及如何在未來使用這種功能。
其中第一個(gè)問題是:這項(xiàng)能力與多年來一直是 BPF 一部分的 BPF helper 機(jī)制有何不同?changelog 中并沒有解釋這一點(diǎn),所以編者只能猜測一下。BPF helper program 這些代碼是專門供 BPF 程序使用的,必須經(jīng)過特別的聲明,而且需要填寫一個(gè) bpf_func_proto 結(jié)構(gòu)并提供給 verifier,可以參看 bpf_map_lookup_elem() 相關(guān)代碼示例。要將現(xiàn)有的內(nèi)核函數(shù)作為一個(gè) BPF helper 使用的話,意味著要寫一個(gè) wrapper 函數(shù),然后再完成上述這些步驟。
用新方法使一個(gè)內(nèi)核函數(shù)可被調(diào)用的話,只需要定義一個(gè)允許調(diào)用的 "check" 函數(shù),剩下的就由 BPF 子系統(tǒng)來完成。人們可能會(huì)認(rèn)為,一開始實(shí)現(xiàn) helper 的時(shí)候就應(yīng)該以這種方式來實(shí)現(xiàn),但其實(shí)很多必備的底層基礎(chǔ)設(shè)施是在 helper 機(jī)制出現(xiàn)多年后才被開發(fā)出來的。比如必須要 BTF,必須要 BPF Linux security module(以前所說的 KRSI)。如果一開始就有這些基礎(chǔ)設(shè)施的話,可能就不會(huì)有 BPF helper 了。
也就是說,BPF helper 的優(yōu)點(diǎn)是只為了 BPF program 使用的,而內(nèi)核函數(shù)是為了被內(nèi)核中各個(gè)部分調(diào)用而存在的。內(nèi)核中沒有穩(wěn)定的 ABI,所以經(jīng)常會(huì)看到 BPF export 出來的內(nèi)核函數(shù)接口的變化甚至比起 BPF helper 接口變化更頻繁。在這個(gè)添加了函數(shù)調(diào)用能力的 commit 中明確說明不保證 ABI:
白名單上的函數(shù)不保證作為固定 ABI。這些函數(shù)已經(jīng)被現(xiàn)有的內(nèi)核 tcp-cc 使用。如果其中任何一個(gè)改變了,那么無論是 in-tree 還是 out-of-tre 的 kernel tcp-cc 實(shí)現(xiàn)代碼都必須更改。
很想知道如果一個(gè)內(nèi)核內(nèi)部的改動(dòng)破壞了某個(gè)很流行的 BPF program,用戶開始抱怨的時(shí)候會(huì)發(fā)生什么。一般來說,提供給 BPF 的功能不算是內(nèi)核 ABI 的一部分,但這個(gè)策略從未被 Linus Torvalds 明確地承認(rèn)或贊同過。
BPF helper 也被設(shè)計(jì)成可以從 BPF 上下文中進(jìn)行安全的調(diào)用。換句話說,就是可以從內(nèi)核之外調(diào)用。常規(guī)的內(nèi)核函數(shù)在編寫時(shí)沒有考慮到可能會(huì)有惡意的調(diào)用者。BPF 子系統(tǒng)作為一個(gè)整體則竭力確保 BPF program 不會(huì)導(dǎo)致崩潰或者被入侵,但該子系統(tǒng)并不能知道在它調(diào)用的內(nèi)核函數(shù)內(nèi)部在做些什么事情,也不能保證給某個(gè)函數(shù)調(diào)用傳遞的所有參數(shù)是符合規(guī)范的。如果把錯(cuò)誤的函數(shù)提供給了 BPF,那么一個(gè)錯(cuò)誤程序或者惡意程序就可能會(huì)利用它們來制造麻煩。
最后,這種機(jī)制看起來有點(diǎn)像在內(nèi)核本身的 export 機(jī)制之外的一個(gè)后門。export symbol 給 module 調(diào)用的時(shí)候都要在相關(guān)代碼旁邊加上 EXPORT_SYMBOL() 這個(gè)聲明,并且這些經(jīng)常引起人們的關(guān)注,以及引發(fā)是否該用這種方式把內(nèi)核內(nèi)部的東西暴露給外界使用的討論。而針對(duì) BPF program 的 export 函數(shù)是一種比較低調(diào)的做法,相關(guān)代碼可能遠(yuǎn)離相關(guān)函數(shù)的定義位置。極端來說,似乎沒有什么辦法可以阻止某人注冊(cè)這樣的檢查函數(shù):
static bool export_the_world(u32 kfunc_btf_id)
{
return true;
}
如果添加了這個(gè)函數(shù)的話,那么幾乎內(nèi)核中所有函數(shù)都可以被一個(gè)正確類型的 BPF program 調(diào)用。這不會(huì)是什么好事情。理論上來說這樣的函數(shù)會(huì)在代碼 review 中被發(fā)現(xiàn),但值得思考的是,有多少人真的 review 過了這組 patch 中加入的 BPF 調(diào)用函數(shù)的測試代碼?這些代碼已經(jīng)被加入到了(完全無關(guān)的)traffic-control classifier program type 中。這組(無害的)代碼將出現(xiàn)在所有啟用了 traffic-control 的系統(tǒng)中??雌饋?export 一個(gè)錯(cuò)誤的函數(shù)給 BPF program 從而有意或無意地創(chuàng)造 bug,并不算困難。
這些問題中某些也許可以通過向 BPF 核心代碼注冊(cè)一個(gè)得到許可的內(nèi)核函數(shù)列表來解決,也就是不能自己來決定要 export 哪些函數(shù)。但目前實(shí)現(xiàn)的代碼并不是這么做的。
盡管如此,這個(gè) BPF function-calling 機(jī)制已經(jīng)被合并,會(huì)被包含在 5.13 版本中。據(jù)推測,在未來的版本中大家足夠警惕,防止內(nèi)核函數(shù)被不恰當(dāng)?shù)?export 出來。如果管理得當(dāng),這個(gè)特性可以被用來為 BPF program 提供許多功能,大大增加了可以用 BPF 做的有用的事情。很期待看到這個(gè)特性的后續(xù)發(fā)展。
全文完
LWN 文章遵循 CC BY-SA 4.0 許可協(xié)議。
長按下面二維碼關(guān)注,關(guān)注 LWN 深度文章以及開源社區(qū)的各種新近言論~
