RM新时代网站-首页

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內(nèi)不再提示

云原生運行時防護系統(tǒng)Tetragon介紹

xCb1_yikoulinux ? 來源:榫卯江湖 ? 作者:榫卯江湖 ? 2022-06-12 15:43 ? 次閱讀

TL;DR

文章較長,代碼很多,可直接拖到文末看講解視頻

背景

在云原生領域中,Cilium是容器管理上最著名的網(wǎng)絡編排、可觀察性、網(wǎng)絡安全的開源軟件?;诟锩约夹geBPF實現(xiàn),并用XDP、TC等功能實現(xiàn)了L3、L4層的防火墻、負載均衡軟件,具備優(yōu)秀的網(wǎng)絡安全處理能力,但在運行時安全上,Cilium一直是缺失的。

2022年5月,在歐洲舉行的KubeCon技術峰會期間,Cilium的母公司Isovalent發(fā)布了云原生運行時防護系統(tǒng)Tetragon[1],填補這一空缺。

Tetragon的面世,意味著與falco、tracee、KubeArmor、datadog-agent等幾款產(chǎn)品正面競爭,eBPF運行時防護領域愈加內(nèi)卷。

Tetragon介紹

摘自Tetragon官方倉庫[2]的產(chǎn)品介紹。

eBPF實時性

Tetragon 是一個運行時安全實時和可觀察性工具。它直接在內(nèi)核中對事件做相應動作,比如執(zhí)行過濾、阻止,無需再將事件發(fā)送到用戶空間處理。

3bf52fcc-e92d-11ec-ba43-dac502259ad0.png

對于可觀察性用例,直接在內(nèi)核中應用過濾器會大大減少觀察開銷。避免昂貴的上下文切換,尤其是對于高頻事件,例如發(fā)送、讀取或寫入操作,減少了大量的內(nèi)存、CPU等資源。

同時,Tetragon提供了豐富的過濾器(文件、套接字、二進制名稱、命名空間等),允許用戶篩選重要且相關的事件,并傳遞給用戶空間。

eBPF靈活性

Tetragon 可以掛載到Linux Kernel中的任何函數(shù),并過濾其參數(shù)、返回值、進程元數(shù)據(jù)(例如,可執(zhí)行文件名稱)、文件和其他屬性。

跟蹤策略通過編寫跟蹤策略,用戶可以解決各種安全性和可觀察性用例。Tetragon社區(qū)中,提供了一些常見的跟蹤策略,可以解決大部分的可觀察性和安全用例。

用戶也可以創(chuàng)建新的規(guī)則策略部署,自定義配置文件,以滿足業(yè)務需求。

eBPF 內(nèi)核感知

Tetragon 通過eBPF鉤子感知Linux Kernel狀態(tài),并將狀態(tài)與Kubernetes用戶策略相結合,以創(chuàng)建由內(nèi)核實時執(zhí)行的規(guī)則,來增強云原生場景的安全防護功能。例如,當容器內(nèi)惡意程序更改其權限時,我們可以創(chuàng)建一個策略來觸發(fā)警報,甚至在進程有機會完成系統(tǒng)調(diào)用并可能運行其他系統(tǒng)調(diào)用之前終止該進程。

Tetragon阻斷實現(xiàn)原理

以上是Tetragon官方的介紹,提到具備阻斷能力,并在技術峰會上,展示了相關阻斷的截圖,有必要了解一下其實現(xiàn)原理。

tetragon的運行原理會在下篇詳細介紹,本篇主要講實時阻斷原理。本文分析的代碼版本為首次發(fā)布的tag v0.8.0[3] ,commit ID:75e49ab。

業(yè)界常見方式

LKM的內(nèi)核模塊、LD_PRELOAD的動態(tài)鏈接庫修改、基于LSM的selinux、seccomp技術等都是常見做內(nèi)核態(tài)/用戶態(tài)運行時阻斷的技術方案,而缺點就比較明顯,系統(tǒng)穩(wěn)定性、規(guī)則靈活性、變更周期等問題比較突出。

3c383e52-e92d-11ec-ba43-dac502259ad0.png

當然,也有使用內(nèi)核模塊方式。方法是把eBPF特性,封裝在內(nèi)核模塊里,再安裝到老版本的內(nèi)核上,這樣,就可以覆蓋更多內(nèi)核版本了。但backport新特性的做法在社區(qū)里很不推薦,維護成本特別高,需要比較大的內(nèi)核研發(fā)團隊與深厚的技術功底。。

云原生生態(tài)中,CNCF的項目Falco具備內(nèi)核模塊與eBPF探針兩套驅動引擎,提供數(shù)據(jù)收集能力。同類產(chǎn)品Tracee也是,還基于LSM接口,實現(xiàn)了一定的防御阻斷能力,同時支持使用者自定義語法配置文件,進行檢測、判斷、阻斷規(guī)則的修改快速更新,以達到更好的防御能力。

3c634bec-e92d-11ec-ba43-dac502259ad0.png

作為云原生領域的容器管理軟件領頭羊,Cilium也會落后,但Linux Security Module[4]鉤子(以下簡稱LSM)需要Linux Kernel 5.7以上版本,而業(yè)界多數(shù)內(nèi)核版本都不會這么新。Cilium有沒有使用LSM類HOOK進行阻斷呢?我們一起來看一下。

配置文件

前面提到,Tetragon靈活性更高,可以讀取配置文件規(guī)則,應用到內(nèi)核態(tài)。以代碼倉庫的crds/examples/open_kill.yaml為例,語法規(guī)則分為如下幾部分

  1. kprobe函數(shù)名
  2. 函數(shù)原型參數(shù)
  3. 進程過濾配置
  4. 參數(shù)過濾配置
  5. 執(zhí)行動作
3c94dfc2-e92d-11ec-ba43-dac502259ad0.jpg

其中,matchActions字段為匹配后的執(zhí)行動作,比如這里的Sigkill

-call:"__x64_sys_write"
syscall:true
args:
-index:0
type:"fd"
-index:1
type:"char_buf"
sizeArgIndex:3
-index:2
type:"size_t"
selectors:
-matchPIDs:
-operator:NotIn
followForks:true
isNamespacePID:true
values:
-0
-1
matchArgs:
-index:0
operator:"Prefix"
values:
-"/etc/passwd"
matchActions:
-action:Sigkill

yaml配置文件的解析在pkg/k8s/apis/cilium.io/v1alpha1/types.go中的TracingPolicySpec結構體中,包含KProbeSpecTracepointSpec, 對應json:"kprobes"json:"tracepoints"兩個json的結構。

typeTracingPolicySpecstruct{
//+kubebuilderOptional
//Alistofkprobespecs.
KProbes[]KProbeSpec`json:"kprobes"`
//+kubebuilderOptional
//Alistoftracepointspecs.
Tracepoints[]TracepointSpec`json:"tracepoints"`
}

同時,Tetragon還支持遠程下發(fā)配置,配置結構與yaml結構是一樣的。這里相比傳統(tǒng)的內(nèi)核模塊等技術方案,靈活性更高。

用戶空間

詳細執(zhí)行流程會在下篇分享,直入主題。

數(shù)據(jù)結構

在項目中,抽象出一些概念:

  1. Tetragon由多個Sensors傳感器構成
  2. Sensor由多個Programs和Maps構成
  3. 每個Program對應eBPF代碼的HOOK函數(shù)
  4. 每個Map是相應Program的bpf的數(shù)據(jù)交互map
3cc19832-e92d-11ec-ba43-dac502259ad0.gif
//ProgramreprentsaBPFprogram.
typeProgramstruct{
//NameisthenameoftheBPFobjectfile.
Namestring
//Attachistheattachmentpoint,e.g.thekernelfunction.
Attachstring
//Labelistheprogramsectionnametoloadfromprogram.
Labelstring
//PinPathisthepinnedpathtothisprogram.Notethisisarelativepath
//basedontheBPFdirectoryFGSisrunningunder.
PinPathstring

//RetProbeindicateswhetherakprobeisakretprobe.
RetProbebool
//ErrorFatalindicateswhetheraprogrammustloadandfatalotherwise.
//Mostprogramwillsetthistotrue.Forexample,kernelfunctionshooks
//maychangeacrossverionssodifferentnamesareattempted,hence
//avoidingfatalingwhenthefirstattemptfails.
ErrorFatalbool

//Needsoverridebpfprogram
Overridebool

//TypeisthetypeofBPFprogram.Forexample,tc,skb,tracepoint,
//etc.
Typestring
LoadStateState

//TraceFDisneededbecausetracepointsareaddeddifferentthankprobes
//forexample.TheFDistokeepareferencetothetracepointprogramin
//ordertodeleteit.TODO:ThiscanbemovedintoloaderDatafor
//tracepoints.
TraceFDint

//LoaderDatarepresentsper-typespecificfields.
LoaderDatainterface{}

//unloaderfortheprogram.nilifnotloaded.
unloaderunloader.Unloader
}

ExecveV53=program.Builder(
"bpf_execve_event_v53.o",
"sched/sched_process_exec",
"tracepoint/sys_execve",
"event_execve",
"execve",
)
3d2390dc-e92d-11ec-ba43-dac502259ad0.jpg

Sensors傳感器加載

默認傳感器注冊

pkg/sensors/tracing包下由兩個文件對傳感器進行默認注冊,分別是generictracepoint.gogenerickprobe.go,寫入如下兩個Sensors到registeredTracingSensors中。

  1. observerKprobeSensor ,kprobe類型HOOK
  2. observerTracepointSensor, tracepoint類型HOOK

同時,還會注冊自定義eBPF probe加載器到registeredProbeLoad中。

//generickprobe.go#Line=43
funcinit(){
kprobe:=&observerKprobeSensor{
name:"kprobesensor",
}
sensors.RegisterProbeType("generic_kprobe",kprobe)
sensors.RegisterTracingSensorsAtInit(kprobe.name,kprobe)
observer.RegisterEventHandlerAtInit(ops.MSG_OP_GENERIC_KPROBE,handleGenericKprobe)
}

策略文件解析

GetSensorsFromParserPolicy方法遍歷所有Sensors,調(diào)用它的SpecHandler方法,解析yaml配置。解析配置后,生成新的傳感器對象,作為整個應用啟動、注入、監(jiān)聽的所有傳感器。

//cmd/tetragon/main.go#line=209
startSensors,err=sensors.GetSensorsFromParserPolicy(&cnf.Spec)

//pkg/sensors/sensors.go#line=160
for_,s:=rangeregisteredTracingSensors{
sensor,err:=s.SpecHandler(spec)
iferr!=nil{
returnnil,err
}
ifsensor==nil{
continue
}
sensors=append(sensors,sensor)
}

新增傳感器

接上,yaml解析器讀取格式后,根據(jù)當前傳感器的類型(kprobe還是tracepoint),處理yaml配置文件對應的HOOK配置。

kprobe類型kprobe類型的hook配置為例,調(diào)用 addGenericKprobeSensors 詳細處理每一個配置內(nèi)容。

//pkg/sensors/tracing/generickprobe.go#line=242-516
funcaddGenericKprobeSensors(kprobes[]v1alpha1.KProbeSpec,btfBaseFilestring)(*sensors.Sensor,error){
//遍歷所有kprobes的元素
fori:=rangekprobes{
f:=&kprobes[i]
funcName:=f.Call
//1.驗證配置文件中配置依賴,比如Sigkill需要內(nèi)核大于5.3
// 2. 解析匹配參數(shù)(進程名、namespace、路徑、五元組等)寫入BTF對象
//3.解析ReturnArg返回值參數(shù),寫入到BTF對象
//4.過濾保留參數(shù),寫入BTF對象
//5.解析Filters邏輯,寫入BTF對象
//6.解析Binary名字到內(nèi)核數(shù)據(jù)結構體
//7.將屬性寫入BTF指針,以便加載
//8.判斷action是否為SIGKILL,并寫入BTF對象
kernelSelectors,err:=selectors.InitKernelSelectors(f)
kprobeEntry:=genericKprobe{
loadArgs:kprobeLoadArgs{
filters:kernelSelectors,
btf:uintptr(btfobj),
retprobe:setRetprobe,
syscall:is_syscall,
},
argSigPrinters:argSigPrinters,
argReturnPrinters:argReturnPrinters,
userReturnFilters:userReturnFilters,
funcName:funcName,
pendingEvents:map[uint64]pendingEvent{},
tableId:idtable.UninitializedEntryID,
}
genericKprobeTable.AddEntry(&kprobeEntry)

//9.設定這個kprobe所需的ebpf字節(jié)碼文件信息

loadProgName:="bpf_generic_kprobe_v53.o"
// 10. 利用如上信息,填充到prog的結構體中。

//11.在prog結構體中,label字段的值都是**kprobe/generic_kprobe**
load:=program.Builder(
path.Join(option.Config.HubbleLib,loadProgName),
funcName,
"kprobe/generic_kprobe",
"kprobe"+"_"+funcName,
"generic_kprobe").
SetLoaderData(kprobeEntry.tableId)
}

//創(chuàng)建生成新的sensor,并返回
return&sensors.Sensor{
Name:"__generic_kprobe_sensors__",
Progs:progs,
Maps:[]*program.Map{},
},nil
}

解析過程如下:

  1. 驗證配置文件中配置依賴,比如Sigkill需要內(nèi)核大于5.3
  2. 解析匹配參數(shù)(進程名、namespace、路徑、五元組等)寫入BTF對象
  3. 解析ReturnArg 返回值參數(shù),寫入到BTF對象
  4. 過濾保留參數(shù),寫入BTF對象
  5. 解析Filters邏輯,寫入BTF對象
  6. 解析Binary名字到內(nèi)核數(shù)據(jù)結構體
  7. 將屬性寫入BTF指針,以便加載
  8. 判斷action是否為SIGKILL,并寫入BTF對象
  9. 設定這個kprobe所需的ebpf字節(jié)碼文件信息
  10. 利用如上信息,填充到prog的結構體中。
  11. 在prog結構體中,label字段的值都是kprobe/generic_kprobe

解析完成后,返回一個新的sensor,并添加到Sensors傳感器數(shù)組中。

至此,完成了配置文件的解析。在這里,阻斷指令的配置,是保存在genericKprobe.loadArgs.filters這個byte數(shù)組中的。

動態(tài)更新

在tetragon項目中,還具備從遠程下發(fā)新的規(guī)則,更新、添加Sensor傳感器功能,相應代碼在pkg/observer/observer.go中,本篇不做過多展開,會在下篇分享。

//InitSensorManagerstartsthesensorcontrollerandsttmanager.
func(k*Observer)InitSensorManager()error{
varerrerror
SensorManager,err=sensors.StartSensorManager(k.bpfDir,k.mapDir,k.ciliumDir)
returnerr
}

eBPF加載與掛載

Sensors啟動

傳感器啟動加載,執(zhí)行流程為obs.Start(ctx, startSensors) -> config.LoadConfig -> load.Load

在Load方法里,對每一個Program元素,調(diào)用observerLoadInstance方法進行加載。

// load.Load

//Loadloadsthesensor,byloadingalltheBPFprogramsandmaps.
func(s*Sensor)Load(stopCtxcontext.Context,bpfDir,mapDir,ciliumDirstring)error{
for_,p:=ranges.Progs{
//...

//加載每個prog
iferr:=observerLoadInstance(stopCtx,bpfDir,mapDir,ciliumDir,p);err!=nil{
returnerr
}
//...
}
}

eBPF Program加載、掛載

在對每個eBPF program進行加載時,會判斷HOOK的類型,針對tracepoint特殊判斷處理。這里還是以Kprobe為例。代碼調(diào)用loadInstance函數(shù),邏輯中判斷是否存在自定義的加載器

  1. 若有,則調(diào)用s.LoadProbe加載;
  2. 若沒有,則調(diào)用loader.LoadKprobeProgram加載;
//pkg/sensors/load.go#Line=297
ifs,ok:=registeredProbeLoad[load.Type];ok{
logger.GetLogger().WithField("Program",load.Name).WithField("Type",load.Type).Infof("Loadprobe")
returns.LoadProbe(LoadProbeArgs{
BPFDir:bpfDir,
MapDir:mapDir,
CiliumDir:ciliumDir,
Load:load,
Version:version,
Verbose:verbose,
})
}
returnloader.LoadKprobeProgram(
version,verbose,
btfObj,
load.Name,
load.Attach,
load.Label,
filepath.Join(bpfDir,load.PinPath),
mapDir,
load.RetProbe)

同樣,以前面提到的observerKprobeSensor類型傳感器,已經(jīng)注冊自己的Probe加載器,那么會走s.LoadProbe()邏輯,之后,調(diào)用loadGenericKprobeSensor() -> loadGenericKprobe()進行加載。

這里的load.Name、load.Attachload.Label的值,來自前面的yaml配置文件讀取部分,值分別為bpf_generic_kprobe_v53.o__x64_sys_write 、 kprobe/generic_kprobe,也就是說,不管是哪個kprobe函數(shù),都會被掛載到kprobe/generic_kprobe上,都被generic_kprobe_event()這個eBPF 函數(shù)處理,起到統(tǒng)一管理的網(wǎng)關作用。

3d75e7b0-e92d-11ec-ba43-dac502259ad0.jpg

kprobe/generic_kprobe對應的eBPF代碼在bpf/process/bpf_generic_kprobe.c文件里,我們在后面內(nèi)核空間代碼詳細分析。

__attribute__((section(("kprobe/generic_kprobe")),used))int
generic_kprobe_event(structpt_regs*ctx)
{
returngeneric_kprobe_start_process_filter(ctx);
}

filters過濾器

loadGenericKprobe()函數(shù)最后一個參數(shù)filters過濾器參數(shù),類型是[4096]byte

funcloadGenericKprobe(bpfDir,mapDirstring,versionint,p*program.Program,btfuintptr,genmapDirstring,filters[4096]byte)error{
}

其內(nèi)容為前面yaml格式解析中的第5步,

kernelSelectors,err:=selectors.InitKernelSelectors(f)

格式構成為

filter := [length][matchPIDs][matchBinaries][matchArgs][matchNamespaces][matchCapabilities][matchNamespaceChanges][matchCapabilityChanges]

這些數(shù)據(jù),也是在內(nèi)核空間eBPF邏輯中,實現(xiàn)參數(shù)匹配,動作響應的判斷依據(jù)。

Cgo函數(shù)調(diào)用

在調(diào)用BPF SYSCALL的實現(xiàn)上,Tetragon沒有使用母公司自己的Cilium/ebpf庫,而是使用CGo包裝了libbpf進行加載。使用的版本還是0.2.0,當前社區(qū)最新版為0.7.0,比較老。(下一篇再講原因)

loadGenericKprobe()函數(shù)調(diào)用bpf.LoadGenericKprobeProgram(),并把filters傳遞給下一個CGO的函數(shù)C.generic_kprobe_loader(),函數(shù)定義在pkg/bpf/loader.go的476行。

intgeneric_kprobe_loader(constintversion,
constintverbosity,
booloverride,
void*btf,
constchar*prog,
constchar*attach,
constchar*label,
constchar*__prog,
constchar*mapdir,
constchar*genmapdir,
void*filters){
structbpf_object*obj;
interr;
obj=generic_loader_args(version,verbosity,override,btf,prog,attach,
label,__prog,mapdir,filters,BPF_PROG_TYPE_KPROBE);
if(!obj){
return-1;
}
//...
}

之后,再調(diào)用CGO的C函數(shù)generic_loader_args()進行BPF SYSCALL調(diào)用,加載eBPF程序,掛載到對應kprobe函數(shù)上。之后,再寫入eBPF Maps。

3d9efe34-e92d-11ec-ba43-dac502259ad0.jpg

eBPF Maps創(chuàng)建寫入

Tetragon使用eBPF Maps進行用戶空間與內(nèi)核空間的配置數(shù)據(jù)交互,比如yaml配置文件中,各種過濾條件,匹配后的處理動作等。

還是以open_kill.yaml為例,涉及了兩類eBPF Map

  1. 特征匹配規(guī)則,也就是配置的內(nèi)容,比如需要保護的文件路徑、IP黑名單等,稱之為filters規(guī)則
  2. 路由分發(fā)規(guī)則,也就是tetragon程序內(nèi)部,用于eBPF HOOK的函數(shù)網(wǎng)關處理各類參數(shù)的自用規(guī)則,用尾調(diào)用Tail Call類型的map實現(xiàn)。
3dc4c182-e92d-11ec-ba43-dac502259ad0.gif

filters規(guī)則Map

generic_loader_args()里,創(chuàng)建了"filter_map" eBPF Map,并將判斷規(guī)則、阻斷規(guī)則filter bytes數(shù)組寫入到這個map里,格式依舊是[4096]byte的字節(jié)流。

//...
char*filter_map="filter_map";
//...
map_fd=bpf_object__find_map_fd_by_name(obj,filter_map);
if(map_fd>=0){
err=bpf_map_update_elem(map_fd,&zero,filter,BPF_ANY);
if(err){
printf("WARNING:mapupdateelem%serror%d
",filter_map,err);
}
}

Tail Call尾調(diào)用Map

在eBPF Program加載部分提到,eBPF Kprobe函數(shù)kprobe/generic_kprobe(即generic_kprobe_event())作為統(tǒng)一的過濾處理網(wǎng)關,針對不同的kprobe,其參數(shù)個數(shù)、參數(shù)類型一定是不一樣的,比如文件讀寫函數(shù)__x64_sys_write有三個參數(shù),分別是

args:
-index:0
type:"fd"
-index:1
type:"char_buf"
sizeArgIndex:3
-index:2
type:"size_t"

而SOCKET連接函數(shù)__x64_connect只有兩個參數(shù),分別是

args:
-index:0
type:"sockfd"
-index:1
type:"sockaddr"

并且,他們的參數(shù)類型也不一樣,作為統(tǒng)一網(wǎng)關,且面對參數(shù)個數(shù)、參數(shù)類型都不一樣的問題,Tetragon在eBPF的解決方案是使用BPF_MAP_TYPE_PROG_ARRAY類型的eBPF Map實現(xiàn),用于尾調(diào)用Tail Call處理。,

kprobe_calls尾調(diào)用Map

structbpf_map_def__attribute__((section("maps"),used))kprobe_calls={
.type=BPF_MAP_TYPE_PROG_ARRAY,
.key_size=sizeof(__u32),
.value_size=sizeof(__u32),
.max_entries=11,
};

kprobe_calls Map寫入

map_bpf=bpf_object__find_map_by_name(obj,"kprobe_calls");
//...
err=bpf_map__pin(map_bpf,map_name);
//...
map_fd=bpf_map__fd(map_bpf);

for(i=0;i11;i++){
structbpf_program*prog;
charprog_name[20];
charpin_name[200];
intfd;

snprintf(prog_name,sizeof(prog_name),"kprobe/%i",i);
prog=bpf_object__find_program_by_title(obj,prog_name);
if(!prog)
gotoout;
fd=bpf_program__fd(prog);
if(fd0){
err=errno;
gotoerr;
}
snprintf(pin_name,sizeof(pin_name),"%s_%i",__prog,i);
bpf_program__unpin(prog,pin_name);
err=bpf_program__pin(prog,pin_name);
if(err){
printf("programpin%stailcallerr%d
",pin_name,err);
gotoerr;
}
err=bpf_map_update_elem(map_fd,&i,&fd,BPF_ANY);
if(err){
printf("mapupdateelemi%i%stailcallerr%d%d
",i,prog_name,err,errno);
gotoerr;
}
}

用戶空間程序,讀取eBPF二進制文件,讀取kprobe開頭的的eBPF函數(shù),以ID作為Key,寫入到kprobe_calls尾調(diào)用表中。

一共11個函數(shù),都在bpf/process/bpf_generic_kprobe.c文件里,分別是:

  1. kprobe/0 對應 generic_kprobe_process_event0
  2. kprobe/1 對應 generic_kprobe_process_event1
  3. kprobe/2 對應 generic_kprobe_process_event2
  4. kprobe/3 對應 generic_kprobe_process_event3
  5. kprobe/4 對應 generic_kprobe_process_event4
  6. kprobe/5 對應 generic_kprobe_process_filter
  7. kprobe/6 對應 generic_kprobe_filter_arg1
  8. kprobe/7 對應 generic_kprobe_filter_arg2
  9. kprobe/8 對應 generic_kprobe_filter_arg3
  10. kprobe/9 對應 generic_kprobe_filter_arg4
  11. kprobe/10 對應 generic_kprobe_filter_arg5

至此,涉及eBPF阻斷功能的用戶空間邏輯全部完成。

內(nèi)核空間

在內(nèi)核空間,入口函數(shù)為用戶空間HOOK的kprobe點 "kprobe/generic_kprobe" ,對應 generic_kprobe_event() 函數(shù)。這函數(shù)內(nèi)部只有一個 **generic_kprobe_start_process_filter()**的調(diào)用。

3e03f398-e92d-11ec-ba43-dac502259ad0.jpg

統(tǒng)一網(wǎng)關觸發(fā)

前面提到,tetragon是把open_kill.yaml中的所有kprobe syscall都交給這個eBPF函數(shù)處理,所以,當__x64_sys_write這些HOOK點觸發(fā)后,邏輯都交給generic_kprobe_start_process_filter()處理。

//bpf/process/bpf_generic_kprobe.c#line=48
staticinline__attribute__((always_inline))int
generic_kprobe_start_process_filter(void*ctx)
{
//...

/*Tailcallintofilters.*/
tail_call(ctx,&kprobe_calls,5);
return0;
}

程序內(nèi)部獲取當前進程信息(struct task_struct、namespace、caps等)后,使用Tail Call尾調(diào)用轉交kprobe_calls Maps的第5個函數(shù)處理,即generic_kprobe_process_filter()

提醒

因eBPF虛擬機寄存器限制,只能獲取前5個參數(shù)。

進程過濾流程

獲取當前進程信息后,進入下一個流程,獲取當前kprobe的進程參數(shù)。即進入generic_kprobe_process_filter()函數(shù)內(nèi)部

//bpf/process/bpf_generic_kprobe.c#Line=146
//...
ret=generic_process_filter(msg,&filter_map,&process_call_heap);
if(ret==PFILTER_CONTINUE)
tail_call(ctx,&kprobe_calls,5);
elseif(ret==PFILTER_ACCEPT)
tail_call(ctx,&kprobe_calls,0);
/*Iffilterdoesnotacceptdropit.Ideallywewould
*logerrorcodesforlaterreview,TBD.
*/
returnPFILTER_REJECT;

這里對kprobe所在進程信息,以及配置文件中信息匹配,判斷是否要走過濾、阻斷流程。流程邏輯如下

PFILTER_ACCEPT 逐個進入5類進程event事件判斷

  1. generic_process_event0()
  2. generic_process_event1()
  3. generic_process_event2()
  4. generic_process_event3()
  5. generic_process_event4()
  6. generic_kprobe_filter_arg1()

PFILTER_CONTINUE直接進入?yún)?shù)判斷

  1. generic_kprobe_filter_arg1()

OTHER 其他情況

直接返回PFILTER_REJECTeBPF HOOK流程。

參數(shù)判斷流程

當前kprobe函數(shù)是否需要過濾判斷完成后,流程轉入真正的判斷邏輯中,即tailcals尾調(diào)用的第6個函數(shù)處理,即generic_kprobe_filter_arg1()

__attribute__((section(("kprobe/6")),used))int
generic_kprobe_filter_arg1(void*ctx)
{
returnfilter_read_arg(ctx,0,&process_call_heap,&filter_map,
&kprobe_calls,&override_tasks);
}

其中,核心處理函數(shù)為filter_read_arg(),第四個參數(shù)&filter_map就是來自用戶空間解析yaml的filters bytes數(shù)組。

filter_read_arg()函數(shù)判斷觸發(fā)當前kprobe的進程filter配置,若沒找到,則調(diào)用kprobe/7kprobe/9的尾調(diào)用函數(shù),逐個查找filter配置。

阻斷動作執(zhí)行

當找到filter配置后,則讀取配置中相應的action參數(shù)類型,開始進行相應動作分類判斷,執(zhí)行相關流程邏輯,這塊都是在__do_action()函數(shù)中完成的。

3e37835c-e92d-11ec-ba43-dac502259ad0.jpg
actions=(structselector_action*)&f[actoff];

postit=do_actions(e,actions,override_tasks);

action動作的類型有下面幾種

  1. ACTION_POST = 0,
  2. ACTION_FOLLOWFD = 1,
  3. ACTION_SIGKILL = 2,
  4. ACTION_UNFOLLOWFD = 3,
  5. ACTION_OVERRIDE = 4,

每個action動作類型都有相應的處理邏輯,本文重點是阻斷的實現(xiàn),那么只需要關注ACTION_SIGKILL類型。

staticinline__attribute__((always_inline))long
__do_action(longi,structmsg_generic_kprobe*e,
structselector_action*actions,structbpf_map_def*override_tasks)
{
intaction=actions->act[i];

switch(action){
caseACTION_UNFOLLOWFD:
caseACTION_FOLLOWFD:
//...
break;
caseACTION_SIGKILL:
if(bpf_core_enum_value(tetragon_args,sigkill))
send_signal(FGS_SIGKILL);
break;
caseACTION_OVERRIDE:

default:
break;
}
if(!err){
e->action=action;
return++i;
}
return-1;
}

可以看到,針對類型,是調(diào)用了send_signal()函數(shù)進行下發(fā)FGS_SIGKILL指令給當前進程,完整阻斷動作。send_signal()函數(shù)是ebpf的內(nèi)置函數(shù),在Kernel 5.3版本[5]里增加。

阻斷演示視頻可以到CNCF (Cloud Native Computing Foundation)的油管觀看:Real Time Security - eBPF for Preventing attacks - Liz Rice, Isovalent[6]

LSM HOOK比較LSM類HOOK是在Kernel 5.7以后才添加。阻斷功能的實現(xiàn),Tetragon選擇send_signal()的方式,有著兼容更多內(nèi)核版本的優(yōu)勢。并且其kprobe的HOOK點上,可以實現(xiàn)網(wǎng)關式通用處理,通過配置方式,更靈活地變更HOOK點,避免更新eBPF字節(jié)碼的方式。

  1. 更靈活
  2. 網(wǎng)關式
  3. 內(nèi)核版本覆蓋多

總結

Tetragon是一個實時識別阻斷的運行時防護系統(tǒng)。具備網(wǎng)關式統(tǒng)一處理抓手,可以覆蓋更多內(nèi)核版本,通過配置文件方式靈活變更HOOK點。在eBPF技術支持下,還具備熱掛載,系統(tǒng)穩(wěn)定性高,程序可靠性高等特點。是主機運行時防護系統(tǒng)HIDS的最佳學習項目。

3e4e1004-e92d-11ec-ba43-dac502259ad0.jpg

筆者水平有限,若有錯誤,歡迎指出,謝謝。

Tetragon阻斷爭論

2022年5月,云原生安全公司Isovalent的CTO宣布開源了其內(nèi)部開發(fā)了多年的基于eBPF安全監(jiān)控和阻斷的方案:Tetragon。稱可以防御容器逃逸的Linux內(nèi)核漏洞。

安全研究人員Felix Wilhelm的質(zhì)疑,在Tetragone: A Lesson in Security Fundamentals[7]認為可以輕易繞過,并用CVE-2021-22555[8]漏洞修改版演示。

這篇文章從標題上都充滿了各種嘲諷,Tetragon單詞加了e,大概是gone的諧音吧。grsecurity是linux 內(nèi)核安全經(jīng)驗非常深厚的大廠,對這個領域比較精通。但Tetragon的優(yōu)勢并不是內(nèi)核底層安全能力。

賽博堡壘(HardenedVault)也撰寫一篇文章,云原生安全Tetragon案例之安全產(chǎn)品自防護[9] 認為該產(chǎn)品必定失敗。

隨著云原生的流行,Linux內(nèi)核安全成為了一個無法繞開的問題,某個容器被攻陷后可以向Linux內(nèi)核發(fā)起攻擊,一旦成功則整個主機都會被攻擊者控制,如果你不想你的產(chǎn)品耗資上百萬美金后攻擊者兩個小時就攻陷的話,那應該認真的考慮是否應該從一開始就建立正確的威脅模型。另外,eBPF機制更適合實現(xiàn)審計監(jiān)控類的安全方案而非防護阻斷類,VED的eBPF版本也僅僅是為審計而設計,剩下的事情你應該讓SIEM和SOC團隊去做,在安全流程上我們也應該遵循KISS(Keep it simple, stupid?。┰瓌t,不是嗎?

我的看法

針對漏洞利用的方法(注:不是漏洞)的防御機制通常會針對三個階段:

  1. Pre-exploitation(前漏洞利用階段)
  2. Exploitation(漏洞利用階段)
  3. Post-exploitation(后漏洞利用階段)

Tetragon的阻斷功能是在Exploitation漏洞利用階段生效的,因為是可以直接阻斷,讓此次漏洞攻擊失敗。其次,認可幾位安全人員的關于Tetragon適用場景說法,更適合阻斷用戶空間的內(nèi)核逃逸漏洞。

否定的聲音來自底層防御的傳統(tǒng)廠商,其實他們沒明白,Tetragon的優(yōu)勢是可以動態(tài)、輕量、無感知的提升防御能力,并不是完全防御所有攻擊方式。

業(yè)務優(yōu)先于安全

在云原生領域,業(yè)務類型多數(shù)是web服務,安全級別不需要那么高,硬件宿主機重啟成本較高,性能要求大,業(yè)務優(yōu)先,安全其次。過于嚴格的安全檢測,占用過多資源,影響業(yè)務運行速度,性價比低,成本高。這是云原生場景不能接受的。但偶爾的入侵行為是能容忍的。所以業(yè)務優(yōu)先級大于安全是第一守則。

安全優(yōu)先于業(yè)務

傳統(tǒng)安全廠商的產(chǎn)品對系統(tǒng)穩(wěn)定性、可用性、性能都有較大影響,且存在熱更新的難題,哪怕解決了,依舊是特別重的方案。在輕量、動態(tài)、熱更新的需求下,顯得特別笨重。

機密數(shù)據(jù)庫等保密程度較高的服務器,數(shù)據(jù)安全大于業(yè)務功能,愿意犧牲性能換取安全性,那么這種場景適合傳統(tǒng)安全廠商的產(chǎn)品。這種場景的規(guī)則是安全優(yōu)先級大于業(yè)務,更適合傳統(tǒng)安全廠商發(fā)揮。

爭議總結

當今互聯(lián)網(wǎng)的服務器市場中,云原生業(yè)務占比越來越高,這將會是Tetragon、Falco、Tracee、Datadog等運行時安全產(chǎn)品愈加內(nèi)卷的動力。

對于用戶來說,根據(jù)自己的業(yè)務特性,選擇相應的防御檢測產(chǎn)品,滿足自己業(yè)務需求。

原文標題:Tetragon阻斷爭論

文章出處:【微信公眾號:一口Linux】歡迎添加關注!文章轉載請注明出處。

審核編輯:湯梓紅
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權轉載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 內(nèi)核
    +關注

    關注

    3

    文章

    1372

    瀏覽量

    40275
  • Linux
    +關注

    關注

    87

    文章

    11292

    瀏覽量

    209318
  • 云原生
    +關注

    關注

    0

    文章

    248

    瀏覽量

    7947

原文標題:Tetragon阻斷爭論

文章出處:【微信號:yikoulinux,微信公眾號:一口Linux】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    如何縮短Vivado的運行時

    在Vivado Implementation階段,有時是有必要分析一下什么原因導致運行時間(runtime)過長,從而找到一些方法來縮短運行時間。
    的頭像 發(fā)表于 05-29 14:37 ?1.4w次閱讀
    如何縮短Vivado的<b class='flag-5'>運行時</b>間

    云原生技術概述 云原生火爆成為升職加薪核心必備

    一、部署指南 二、集群方案 k8s 針對不同環(huán)境和場景可以使用不同的方案,涵蓋網(wǎng)絡、存儲、運行時、 Ingress、Metrics 等。 三、最佳實踐
    的頭像 發(fā)表于 07-27 10:23 ?1303次閱讀

    如何檢查Linux服務器的運行時

    Linux 中的 uptime 用于查看系統(tǒng)啟動后的運行時間。它是一個比較簡單的 Linux 命令,可以不帶參數(shù)直接運行。
    發(fā)表于 11-25 15:25 ?1.5w次閱讀
    如何檢查Linux服務器的<b class='flag-5'>運行時</b>間

    只需 6 步,你就可以搭建一個云原生操作系統(tǒng)原型

    。第二步在操作系統(tǒng)之上,我們需要一個能夠運行容器的運行時。 傳統(tǒng)上,云原生和容器有非常強的關系,也就是說云原生是通過容器催生出來的。Linu
    發(fā)表于 09-15 14:01

    紫金橋組態(tài)軟件新的功能_運行時組態(tài)

    運行時組態(tài)是組態(tài)軟件新近提出的新的概念。運行時組態(tài)是在運行環(huán)境下對已有工程進行修改,添加新的功能。它不同于在線組態(tài),在線組態(tài)是在工程運行的同時,進入組態(tài)環(huán)境,在組態(tài)環(huán)境中對工程進行修改
    發(fā)表于 10-13 16:17 ?2次下載
    紫金橋組態(tài)軟件新的功能_<b class='flag-5'>運行時</b>組態(tài)

    2021年最熱門的云原生存儲解決方案之一:容器原生存儲

    能夠在容器內(nèi)運行。結合諸如StatefulSets之類的K8s設計,它提供了可靠性和穩(wěn)定性,可以在生產(chǎn)環(huán)境中運行任務關鍵型工作負載。 與容器運行時一起,容器原生存儲和容器本機聯(lián)網(wǎng)構成了
    的頭像 發(fā)表于 01-06 17:48 ?2731次閱讀
    2021年最熱門的<b class='flag-5'>云原生</b>存儲解決方案之一:容器<b class='flag-5'>原生</b>存儲

    如何高效測量ECU的運行時

    ,最終可能會引起運行時間方面的問題。這在項目后期需要大量的時間和金錢來解決。如果不能掌握系統(tǒng)運行狀態(tài),則很難發(fā)現(xiàn)系統(tǒng)內(nèi)缺陷的根源。 解決方案 將TA軟件工具套件與VX1000測量標定
    的頭像 發(fā)表于 10-28 11:05 ?2211次閱讀

    Go運行時:4年之后

    自 2018 年以來,Go GC,以及更廣泛的 Go 運行時,一直在穩(wěn)步改進。近日,Go 社區(qū)總結了 4 年來 Go 運行時的一些重要變化。
    的頭像 發(fā)表于 11-30 16:21 ?825次閱讀

    如何在AUTOSAR OS系統(tǒng)運行時使用事件Event呢?

    在AUTOSAR OS系統(tǒng)中,事件用于向任務發(fā)送信號信息。本節(jié)解釋事件是什么,如何配置它們以及如何在運行時使用它們。
    發(fā)表于 05-22 10:04 ?2721次閱讀
    如何在AUTOSAR OS<b class='flag-5'>系統(tǒng)</b><b class='flag-5'>運行時</b>使用事件Event呢?

    ch32v307記錄程序運行時

    ,不僅會降低用戶的體驗,甚至可能會導致系統(tǒng)的崩潰。 因此,在程序設計和調(diào)試中,我們常常需要記錄程序的運行時間,并通過不斷的優(yōu)化來提升程序的性能。本文將介紹如何在各種編程語言中記錄程序運行時
    的頭像 發(fā)表于 08-22 15:53 ?901次閱讀

    Xilinx運行時(XRT)發(fā)行說明

    電子發(fā)燒友網(wǎng)站提供《Xilinx運行時(XRT)發(fā)行說明.pdf》資料免費下載
    發(fā)表于 09-14 10:01 ?0次下載
    Xilinx<b class='flag-5'>運行時</b>(XRT)發(fā)行說明

    如何保證它們?nèi)萜?b class='flag-5'>運行時的安全?

    緊密耦合的容器運行時繼承了主機操作系統(tǒng)的安全態(tài)勢和攻擊面。運行時或主機內(nèi)核中的任何漏洞及其利用都會成為攻擊者的潛在切入點。
    的頭像 發(fā)表于 11-03 15:24 ?669次閱讀

    jvm運行時內(nèi)存區(qū)域劃分

    的內(nèi)存區(qū)域劃分對于了解Java程序的內(nèi)存使用非常重要,本文將詳細介紹JVM運行時的內(nèi)存區(qū)域劃分。 JVM運行時內(nèi)存區(qū)域主要劃分為以下幾個部分: 程序計數(shù)器(Program Counter
    的頭像 發(fā)表于 12-05 14:08 ?528次閱讀

    三菱plc累計運行時間怎么編程

    具有重要意義。本文將詳細介紹如何使用三菱PLC編程實現(xiàn)累計運行時間的統(tǒng)計功能。 一、概述 累計運行時間是指設備或系統(tǒng)在一定時間內(nèi)的總運行時
    的頭像 發(fā)表于 06-20 11:31 ?2276次閱讀

    什么是云原生MLOps平臺

    云原生MLOps平臺,是指利用云計算的基礎設施和開發(fā)工具,來構建、部署和管理機器學習模型的全生命周期的平臺。以下,是對云原生MLOps平臺的介紹,由AI部落小編整理。
    的頭像 發(fā)表于 12-12 13:13 ?78次閱讀
    RM新时代网站-首页