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

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

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

云原生運(yùn)行時(shí)防護(hù)系統(tǒng)Tetragon介紹

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

TL;DR

文章較長(zhǎng),代碼很多,可直接拖到文末看講解視頻。

背景

在云原生領(lǐng)域中,Cilium是容器管理上最著名的網(wǎng)絡(luò)編排、可觀察性、網(wǎng)絡(luò)安全的開(kāi)源軟件。基于革命性技術(shù)eBPF實(shí)現(xiàn),并用XDP、TC等功能實(shí)現(xiàn)了L3、L4層的防火墻、負(fù)載均衡軟件,具備優(yōu)秀的網(wǎng)絡(luò)安全處理能力,但在運(yùn)行時(shí)安全上,Cilium一直是缺失的。

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

Tetragon的面世,意味著與falco、tracee、KubeArmor、datadog-agent等幾款產(chǎn)品正面競(jìng)爭(zhēng),eBPF運(yùn)行時(shí)防護(hù)領(lǐng)域愈加內(nèi)卷。

Tetragon介紹

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

eBPF實(shí)時(shí)性

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

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

對(duì)于可觀察性用例,直接在內(nèi)核中應(yīng)用過(guò)濾器會(huì)大大減少觀察開(kāi)銷。避免昂貴的上下文切換,尤其是對(duì)于高頻事件,例如發(fā)送、讀取或?qū)懭氩僮?,減少了大量的內(nèi)存、CPU等資源。

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

eBPF靈活性

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

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

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

eBPF 內(nèi)核感知

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

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

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

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

業(yè)界常見(jiàn)方式

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

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

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

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

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

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

配置文件

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

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

其中,matchActions字段為匹配后的執(zhí)行動(dòng)作,比如這里的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結(jié)構(gòu)體中,包含KProbeSpecTracepointSpec, 對(duì)應(yīng)json:"kprobes"json:"tracepoints"兩個(gè)json的結(jié)構(gòu)。

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

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

用戶空間

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

數(shù)據(jù)結(jié)構(gòu)

在項(xiàng)目中,抽象出一些概念:

  1. Tetragon由多個(gè)Sensors傳感器構(gòu)成
  2. Sensor由多個(gè)Programs和Maps構(gòu)成
  3. 每個(gè)Program對(duì)應(yīng)eBPF代碼的HOOK函數(shù)
  4. 每個(gè)Map是相應(yīng)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傳感器加載

默認(rèn)傳感器注冊(cè)

pkg/sensors/tracing包下由兩個(gè)文件對(duì)傳感器進(jìn)行默認(rèn)注冊(cè),分別是generictracepoint.gogenerickprobe.go,寫入如下兩個(gè)Sensors到registeredTracingSensors中。

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

同時(shí),還會(huì)注冊(cè)自定義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配置。解析配置后,生成新的傳感器對(duì)象,作為整個(gè)應(yīng)用啟動(dòng)、注入、監(jiān)聽(tīng)的所有傳感器。

//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ù)當(dāng)前傳感器的類型(kprobe還是tracepoint),處理yaml配置文件對(duì)應(yīng)的HOOK配置。

kprobe類型kprobe類型的hook配置為例,調(diào)用 addGenericKprobeSensors 詳細(xì)處理每一個(gè)配置內(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.驗(yàn)證配置文件中配置依賴,比如Sigkill需要內(nèi)核大于5.3
// 2. 解析匹配參數(shù)(進(jìn)程名、namespace、路徑、五元組等)寫入BTF對(duì)象
//3.解析ReturnArg返回值參數(shù),寫入到BTF對(duì)象
//4.過(guò)濾保留參數(shù),寫入BTF對(duì)象
//5.解析Filters邏輯,寫入BTF對(duì)象
//6.解析Binary名字到內(nèi)核數(shù)據(jù)結(jié)構(gòu)體
//7.將屬性寫入BTF指針,以便加載
//8.判斷action是否為SIGKILL,并寫入BTF對(duì)象
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.設(shè)定這個(gè)kprobe所需的ebpf字節(jié)碼文件信息

loadProgName:="bpf_generic_kprobe_v53.o"
// 10. 利用如上信息,填充到prog的結(jié)構(gòu)體中。

//11.在prog結(jié)構(gòu)體中,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
}

解析過(guò)程如下:

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

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

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

動(dòng)態(tài)更新

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

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

eBPF加載與掛載

Sensors啟動(dòng)

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

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

// load.Load

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

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

eBPF Program加載、掛載

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

  1. 若有,則調(diào)用s.LoadProbe加載;
  2. 若沒(méi)有,則調(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)注冊(cè)自己的Probe加載器,那么會(huì)走s.LoadProbe()邏輯,之后,調(diào)用loadGenericKprobeSensor() -> loadGenericKprobe()進(jìn)行加載。

這里的load.Name、load.Attach、load.Label的值,來(lái)自前面的yaml配置文件讀取部分,值分別為bpf_generic_kprobe_v53.o、 __x64_sys_write 、 kprobe/generic_kprobe,也就是說(shuō),不管是哪個(gè)kprobe函數(shù),都會(huì)被掛載到kprobe/generic_kprobe上,都被generic_kprobe_event()這個(gè)eBPF 函數(shù)處理,起到統(tǒng)一管理的網(wǎng)關(guān)作用。

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

kprobe/generic_kprobe對(duì)應(yīng)的eBPF代碼在bpf/process/bpf_generic_kprobe.c文件里,我們?cè)诤竺鎯?nèi)核空間代碼詳細(xì)分析。

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

filters過(guò)濾器

loadGenericKprobe()函數(shù)最后一個(gè)參數(shù)filters過(guò)濾器參數(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)

格式構(gòu)成為

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

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

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

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

loadGenericKprobe()函數(shù)調(diào)用bpf.LoadGenericKprobeProgram(),并把filters傳遞給下一個(gè)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()進(jìn)行BPF SYSCALL調(diào)用,加載eBPF程序,掛載到對(duì)應(yīng)kprobe函數(shù)上。之后,再寫入eBPF Maps。

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

eBPF Maps創(chuàng)建寫入

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

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

  1. 特征匹配規(guī)則,也就是配置的內(nèi)容,比如需要保護(hù)的文件路徑、IP黑名單等,稱之為filters規(guī)則
  2. 路由分發(fā)規(guī)則,也就是tetragon程序內(nèi)部,用于eBPF HOOK的函數(shù)網(wǎng)關(guān)處理各類參數(shù)的自用規(guī)則,用尾調(diào)用Tail Call類型的map實(shí)現(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ù)組寫入到這個(gè)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)一的過(guò)濾處理網(wǎng)關(guān),針對(duì)不同的kprobe,其參數(shù)個(gè)數(shù)、參數(shù)類型一定是不一樣的,比如文件讀寫函數(shù)__x64_sys_write有三個(gè)參數(shù),分別是

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

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

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

并且,他們的參數(shù)類型也不一樣,作為統(tǒng)一網(wǎng)關(guān),且面對(duì)參數(shù)個(gè)數(shù)、參數(shù)類型都不一樣的問(wèn)題,Tetragon在eBPF的解決方案是使用BPF_MAP_TYPE_PROG_ARRAY類型的eBPF Map實(shí)現(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二進(jìn)制文件,讀取kprobe開(kāi)頭的的eBPF函數(shù),以ID作為Key,寫入到kprobe_calls尾調(diào)用表中。

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

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

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

內(nèi)核空間

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

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

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

前面提到,tetragon是把open_kill.yaml中的所有kprobe syscall都交給這個(gè)eBPF函數(shù)處理,所以,當(dāng)__x64_sys_write這些HOOK點(diǎn)觸發(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)部獲取當(dāng)前進(jìn)程信息(struct task_struct、namespace、caps等)后,使用Tail Call尾調(diào)用轉(zhuǎn)交kprobe_calls Maps的第5個(gè)函數(shù)處理,即generic_kprobe_process_filter()。

提醒

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

進(jìn)程過(guò)濾流程

獲取當(dāng)前進(jìn)程信息后,進(jìn)入下一個(gè)流程,獲取當(dāng)前kprobe的進(jìn)程參數(shù)。即進(jìn)入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;

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

PFILTER_ACCEPT 逐個(gè)進(jìn)入5類進(jìn)程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直接進(jìn)入?yún)?shù)判斷

  1. generic_kprobe_filter_arg1()

OTHER 其他情況

直接返回PFILTER_REJECTeBPF HOOK流程。

參數(shù)判斷流程

當(dāng)前kprobe函數(shù)是否需要過(guò)濾判斷完成后,流程轉(zhuǎn)入真正的判斷邏輯中,即tailcals尾調(diào)用的第6個(gè)函數(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(),第四個(gè)參數(shù)&filter_map就是來(lái)自用戶空間解析yaml的filters bytes數(shù)組

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

阻斷動(dòng)作執(zhí)行

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

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

postit=do_actions(e,actions,override_tasks);

action動(dòng)作的類型有下面幾種

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

每個(gè)action動(dòng)作類型都有相應(yīng)的處理邏輯,本文重點(diǎn)是阻斷的實(shí)現(xiàn),那么只需要關(guā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;
}

可以看到,針對(duì)類型,是調(diào)用了send_signal()函數(shù)進(jìn)行下發(fā)FGS_SIGKILL指令給當(dāng)前進(jìn)程,完整阻斷動(dòng)作。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以后才添加。阻斷功能的實(shí)現(xiàn),Tetragon選擇send_signal()的方式,有著兼容更多內(nèi)核版本的優(yōu)勢(shì)。并且其kprobe的HOOK點(diǎn)上,可以實(shí)現(xiàn)網(wǎng)關(guān)式通用處理,通過(guò)配置方式,更靈活地變更HOOK點(diǎn),避免更新eBPF字節(jié)碼的方式。

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

總結(jié)

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

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

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

Tetragon阻斷爭(zhēng)論

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

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

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

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

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

我的看法

針對(duì)漏洞利用的方法(注:不是漏洞)的防御機(jī)制通常會(huì)針對(duì)三個(gè)階段:

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

Tetragon的阻斷功能是在Exploitation漏洞利用階段生效的,因?yàn)槭强梢灾苯幼钄啵尨舜温┒垂羰?。其次,認(rèn)可幾位安全人員的關(guān)于Tetragon適用場(chǎng)景說(shuō)法,更適合阻斷用戶空間的內(nèi)核逃逸漏洞。

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

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

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

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

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

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

爭(zhēng)議總結(jié)

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

對(duì)于用戶來(lái)說(shuō),根據(jù)自己的業(yè)務(wù)特性,選擇相應(yīng)的防御檢測(cè)產(chǎn)品,滿足自己業(yè)務(wù)需求。

原文標(biāo)題:Tetragon阻斷爭(zhēng)論

文章出處:【微信公眾號(hào):一口Linux】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

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

    關(guān)注

    3

    文章

    1346

    瀏覽量

    40152
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11161

    瀏覽量

    208460
  • 云原生
    +關(guān)注

    關(guān)注

    0

    文章

    239

    瀏覽量

    7924

原文標(biāo)題:Tetragon阻斷爭(zhēng)論

文章出處:【微信號(hào):yikoulinux,微信公眾號(hào):一口Linux】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    如何縮短Vivado的運(yùn)行時(shí)

    在Vivado Implementation階段,有時(shí)是有必要分析一下什么原因?qū)е?b class='flag-5'>運(yùn)行時(shí)間(runtime)過(guò)長(zhǎng),從而找到一些方法來(lái)縮短運(yùn)行時(shí)間。
    的頭像 發(fā)表于 05-29 14:37 ?1.4w次閱讀
    如何縮短Vivado的<b class='flag-5'>運(yùn)行時(shí)</b>間

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

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

    如何檢查L(zhǎng)inux服務(wù)器的運(yùn)行時(shí)

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

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

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

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

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

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

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

    如何高效測(cè)量ECU的運(yùn)行時(shí)

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

    Go運(yùn)行時(shí):4年之后

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

    什么是Kubernetes容器運(yùn)行時(shí)CRI

    起初,Docker是事實(shí)上的容器技術(shù)標(biāo)準(zhǔn),Kubernetes v1.5之前的代碼中直接調(diào)用Docker API,實(shí)現(xiàn)容器運(yùn)行時(shí)的相關(guān)操作。
    的頭像 發(fā)表于 02-20 16:22 ?1413次閱讀
    什么是Kubernetes容器<b class='flag-5'>運(yùn)行時(shí)</b>CRI

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

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

    ch32v307記錄程序運(yùn)行時(shí)

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

    Xilinx運(yùn)行時(shí)(XRT)發(fā)行說(shuō)明

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

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

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

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

    的內(nèi)存區(qū)域劃分對(duì)于了解Java程序的內(nèi)存使用非常重要,本文將詳細(xì)介紹JVM運(yùn)行時(shí)的內(nèi)存區(qū)域劃分。 JVM運(yùn)行時(shí)內(nèi)存區(qū)域主要?jiǎng)澐譃橐韵聨讉€(gè)部分: 程序計(jì)數(shù)器(Program Counter
    的頭像 發(fā)表于 12-05 14:08 ?470次閱讀

    三菱plc累計(jì)運(yùn)行時(shí)間怎么編程

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