計(jì)與 fuzzing 實(shí)踐)
Wasmtime 編譯器混沌模式探秘cranelift-control 控制平面的設(shè)計(jì)與 fuzzing 實(shí)踐【免費(fèi)下載鏈接】wasmtimeA lightweight WebAssembly runtime that is fast, secure, and standards-compliant項(xiàng)目地址: https://gitcode.com/gh_mirrors/wa/wasmtime導(dǎo)讀混沌模式chaos mode是 Wasmtime 項(xiàng)目中 Cranelift 編譯器面向模糊測(cè)試fuzzing的一項(xiàng)內(nèi)部編譯特性它允許在編譯器關(guān)鍵路徑上注入偽隨機(jī)擾動(dòng)從而定向地?cái)_動(dòng)啟發(fā)式優(yōu)化、指令選擇等邏輯幫助發(fā)現(xiàn)常規(guī)測(cè)試難以觸達(dá)的編譯器缺陷。本文圍繞倉(cāng)庫(kù)中 cranelift/control/README.md 所定義的控制平面ControlPlane展開(kāi)完整講解其 crate 定位、feature 門(mén)控的零開(kāi)銷(xiāo)設(shè)計(jì)、核心 API、燃料限制fuel limit二分定位法并結(jié)合源碼給出其在 egraph 優(yōu)化、機(jī)器碼發(fā)射等環(huán)節(jié)的真實(shí)使用證據(jù)。讀完本文你將理解如何為一個(gè) fuzz target 啟用混沌模式、如何用--fuel參數(shù)定位觸發(fā)缺陷的擾動(dòng)點(diǎn)以及該設(shè)計(jì)如何做到在發(fā)布構(gòu)建中零性能影響。一、什么是混沌模式與 ControlPlane根據(jù) cranelift/control/README.md 的開(kāi)篇定義This crate contains the control plane for chaos mode. It can be used to inject pseudo-random perturbations into specific sections in the code while fuzzing.也就是說(shuō)cranelift-control這個(gè) crate 是混沌模式的控制平面在 fuzzing 過(guò)程中向代碼中的特定位置注入偽隨機(jī)擾動(dòng)。更詳細(xì)的定位見(jiàn) crate 的庫(kù)級(jí)文檔 cranelift/control/src/lib.rs當(dāng)混沌模式特性關(guān)閉時(shí)默認(rèn)情況ControlPlane是一個(gè)零大小類(lèi)型zero-sized type會(huì)被編譯器完全優(yōu)化掉當(dāng)特性開(kāi)啟時(shí)ControlPlane提供在代碼特定位置獲取偽隨機(jī)性的功能它適用于針對(duì)編譯器內(nèi)部的定向 fuzzing例如操縱啟發(fā)式優(yōu)化manipulate heuristic optimizations、破壞未定義的寄存器位clobber undefined register bits等。從 Cargo.toml 的description字段White-box fuzz testing framework即白盒模糊測(cè)試框架可以看出這個(gè) crate 的定位非常明確它不是運(yùn)行時(shí)組件而是編譯器的自檢測(cè)試基礎(chǔ)設(shè)施。二、feature 門(mén)控零開(kāi)銷(xiāo)的抽象設(shè)計(jì)混沌模式的核心設(shè)計(jì)約束是絕對(duì)不能拖累正常編譯性能。README 明確指出Its compilation is feature-gated to prevent any performance impact on release builds.該 crate 的編譯通過(guò) feature 門(mén)控以防止對(duì)發(fā)布構(gòu)建產(chǎn)生任何性能影響。2.1 特性矩陣查看 cranelift/control/Cargo.toml 可以確認(rèn)完整的特性定義[features] default [fuzz] # Enable fuzzing support with arbitrary. Does not work on no_std targets. fuzz [dep:arbitrary] # Turn on chaos mode. # Without this feature, a zero-sized dummy will be compiled # for the control plane. chaos [fuzz]三個(gè)特性之間的關(guān)系如下特性含義說(shuō)明default默認(rèn)啟用fuzz僅啟用 arbitrary 支持混沌模式仍處于關(guān)閉狀態(tài)fuzz啟用arbitrary依賴(lài)使ControlPlane可以基于任意字節(jié)流構(gòu)造依賴(lài)stdchaos啟用混沌模式隱式啟用fuzz依賴(lài)std關(guān)閉時(shí)編譯零大小 shim也就是說(shuō)發(fā)布構(gòu)建默認(rèn)關(guān)閉混沌模式是由default [fuzz]且未包含chaos保證的——普通構(gòu)建下chaos特性不存在源代碼層面就直接選擇了空實(shí)現(xiàn)分支。2.2 雙實(shí)現(xiàn)分支cranelift/control/src/lib.rs 通過(guò)條件編譯在兩條實(shí)現(xiàn)之間切換#[cfg(not(feature chaos))] mod zero_sized; #[cfg(not(feature chaos))] pub use zero_sized::*; #[cfg(feature chaos)] mod chaos; #[cfg(feature chaos)] pub use chaos::*;開(kāi)啟chaos時(shí)編譯 cranelift/control/src/chaos.rs 中的真實(shí)實(shí)現(xiàn)關(guān)閉chaos時(shí)編譯 cranelift/control/src/zero_sized.rs 中的零大小 shim。零大小 shim 的設(shè)計(jì)意圖在 cranelift/control/src/zero_sized.rs 的文件頭注釋中寫(xiě)得很清楚它使 cranelift 各處可以無(wú)條件使用ControlPlane類(lèi)型及其方法而無(wú)需在調(diào)用方加條件編譯方法全部為#[inline]空實(shí)現(xiàn)編譯器可以輕易將其內(nèi)聯(lián)消除。例如/// Returns a pseudo-random boolean. This variant is used when chaos /// mode is disabled. It always returns false. #[inline] pub fn get_decision(mut self) - bool { false }這保證了調(diào)用點(diǎn)在正常編譯路徑中不產(chǎn)生任何運(yùn)行時(shí)開(kāi)銷(xiāo)這正是feature-gated 防止性能影響的落地方式。三、ControlPlane 的核心 API混沌模式開(kāi)啟時(shí)真實(shí)實(shí)現(xiàn)位于 cranelift/control/src/chaos.rs。ControlPlane的結(jié)構(gòu)體定義如下#[derive(Debug, Clone, Default)] pub struct ControlPlane { data: Vecu8, // 偽隨機(jī)字節(jié)流由 arbitrary 構(gòu)造而來(lái) fuel: Optionu8,// 燃料限制None 表示未啟用 tmp: Vecu8, // 臨時(shí)緩沖避免 Unstructured 內(nèi)部重復(fù)堆分配 }其中data是偽隨機(jī)數(shù)據(jù)的來(lái)源fuel是擾動(dòng)次數(shù)上限tmp是一次性分配、反復(fù)復(fù)用的優(yōu)化緩沖源碼注釋明確指出這是為了避免Unstructured內(nèi)部額外的堆分配參見(jiàn)shuffle的實(shí)現(xiàn)。3.1 兩種構(gòu)造方式lib.rs 的文檔說(shuō)明了獲取ControlPlane的兩種途徑arbitrary通過(guò)arbitrary::Unstructured從 fuzz 輸入字節(jié)流中構(gòu)造真材實(shí)料的控制平面需要fuzz特性默認(rèn)開(kāi)啟default構(gòu)造一個(gè)空控制平面所有查詢(xún)始終返回默認(rèn)值。對(duì)應(yīng)實(shí)現(xiàn)中Arbitrarytrait 的 impl 直接委托給Self::new(u.arbitrary()?)即把整個(gè)剩余字節(jié)流作為data而Default派生得到的則是空data、fuel None的空平面。3.2 四個(gè)擾動(dòng)查詢(xún)方法方法簽名混沌模式下的行為關(guān)閉/數(shù)據(jù)耗盡時(shí)的行為get_decisionfn get_decision(mut self) - bool從data尾部彈出一個(gè)字節(jié)取最低位作為偽隨機(jī)布爾值始終返回falseget_arbitraryfn get_arbitraryT: Arbitrary Default(mut self) - T用剩余字節(jié)構(gòu)造一個(gè)T類(lèi)型的任意值返回T::default()shufflefn shuffleT(mut self, slice: mut [T])對(duì)切片執(zhí)行基于Unstructured::choose_index的偽隨機(jī)置換Fisher–Yates 風(fēng)格保持切片不變shuffledfn shuffledT(mut self, iter: impl IteratorItem T) - impl IteratorItem T收集迭代器元素后打亂再以新順序返回保持原順序兩個(gè)值得注意的實(shí)現(xiàn)細(xì)節(jié)get_decision每次調(diào)用都會(huì)消費(fèi)一個(gè)字節(jié)因此擾動(dòng)決策是有消耗的與下面的燃料機(jī)制天然銜接get_arbitrary與shuffle在消費(fèi)完剩余字節(jié)后會(huì)通過(guò)u.take_rest()取出剩余部分拷貝進(jìn)tmp后與self.data交換從而每次調(diào)用都從data的開(kāi)頭重新開(kāi)始解析同時(shí)復(fù)用預(yù)分配緩沖避免反復(fù)分配堆內(nèi)存。shuffle的核心置換邏輯源于arbitrarycrate 官方示例如下let mut to_permute mut slice[..]; while to_permute.len() 1 { if let Ok(idx) u.choose_index(to_permute.len()) { to_permute.swap(0, idx); to_permute mut to_permute[1..]; } else { break; } }即逐步將未處理前綴的某個(gè)隨機(jī)位置元素交換到當(dāng)前首位直到字節(jié)流耗盡。四、燃料限制用二分搜索定位缺陷觸發(fā)點(diǎn)4.1 為什么需要燃料混沌模式會(huì)注入大量擾動(dòng)。當(dāng)某個(gè)擾動(dòng)觸發(fā)編譯器 bug 時(shí)很難立即判斷究竟是眾多擾動(dòng)中的哪一個(gè)所為。因此 lib.rs 文檔給出了燃料限制fuel limit這一機(jī)制When a perturbation introduced by chaos mode triggers a bug, it may not be immediately clear which of the introduced perturbations was the trigger. The fuel limit can then be used to binary-search for the trigger. It limits the number of perturbations introduced by the control plane.燃料限制規(guī)定了控制平面在停止前最多引入的擾動(dòng)次數(shù)從而允許對(duì)觸發(fā)點(diǎn)進(jìn)行二分搜索。4.2 實(shí)現(xiàn)與語(yǔ)義set_fuel的實(shí)現(xiàn)cranelift/control/src/chaos.rs/// Set the fuel limit. Zero is interpreted as the /// fuel limit being deactivated, consistent with the cranelift setting /// control_plane_fuel. pub fn set_fuel(mut self, fuel: u8) { self.fuel (fuel ! 0).then_some(fuel) }注意一個(gè)易混淆的語(yǔ)義fuel 0表示停用燃料限制而非立即耗盡這與源碼注釋中提到的 cranelift 設(shè)置項(xiàng)control_plane_fuel保持一致只有Some(f)且f遞減到 0 時(shí)才表示擾動(dòng)次數(shù)已用盡。consume_fuel的內(nèi)部邏輯fn consume_fuel(mut self) - bool { match self.fuel { None true, // fuel deactivated Some(f) if f 0 false, // no more fuel Some(ref mut f) { *f - 1; true } } }每個(gè)擾動(dòng)查詢(xún)?nèi)鏶et_decision都會(huì)先調(diào)用consume_fuel成功才真正實(shí)施擾動(dòng)。由于燃料是u8其取值范圍為 0~255足以支撐對(duì)單個(gè)函數(shù)編譯過(guò)程擾動(dòng)點(diǎn)數(shù)量的精細(xì)控制。4.3 命令行接入方式README 給出了典型的調(diào)用方式——通過(guò) fuzz target 的命令行參數(shù)傳入cargo fuzz run --features chaos $TARGET -- --fuel16其中--features chaos開(kāi)啟混沌模式特性--fuel16表示最多允許 16 次擾動(dòng)。當(dāng) bug 觸發(fā)后可以不斷對(duì)折調(diào)整 fuel 值如 16 - 8 - 4 - 2 - 1逐步縮小嫌疑擾動(dòng)集合最終定位到具體是哪一次擾動(dòng)哪個(gè)決策點(diǎn)觸發(fā)了缺陷。在倉(cāng)庫(kù)的 fuzz target 中這一參數(shù)的解析實(shí)現(xiàn)在 fuzz/fuzz_targets/cranelift-fuzzgen.rslet fuel: u8 std::env::args() .find_map(|arg| arg.strip_prefix(--fuel).map(|s| s.to_owned())) .map(|fuel| fuel.parse().expect(fuel should be a valid integer)) .unwrap_or_default(); for i in 0..testcase.ctrl_planes.len() { testcase.ctrl_planes[i].set_fuel(fuel) }注意這里對(duì)每一個(gè)待編譯函數(shù)的控制平面都應(yīng)用同一個(gè) fuel 值默認(rèn)不傳參數(shù)時(shí) fuel 為 0對(duì)應(yīng)燃料限制停用。五、混沌模式在 Cranelift 編譯器中的真實(shí)落點(diǎn)僅了解 API 還不夠混沌模式的價(jià)值體現(xiàn)在編譯流水線(xiàn)的各個(gè)決策點(diǎn)。下面是從源碼中可直接確認(rèn)的使用位置。5.1 傳遞鏈路ControlPlane從 cranelift/codegen/src/context.rs 的Context::compile一路傳入compile-compile_and_emit/optimize-egraph_pass-compile_stencil最終貫穿優(yōu)化與機(jī)器碼生成階段。cranelift/codegen/Cargo.toml中聲明了cranelift-control { workspace true }其std特性會(huì)連帶啟用cranelift-control/fuzz保證常規(guī)構(gòu)建下ControlPlane的 shim 可用。5.2 egraph 優(yōu)化故意選最差解eclass 優(yōu)化 的compute_best_values會(huì)先做一次決策// We cant make random decisions inside the fixpoint loop below // because that could cause values to change on every iteration of the // loop, which would make the loop never terminate. So in chaos testing // mode we need a form of making suboptimal decisions that is fully // deterministic. We choose to simply make the worst decision we know // how to do instead of the best. let use_worst self.ctrl_plane.get_decision();這是一個(gè)極具代表性的設(shè)計(jì)細(xì)節(jié)在不動(dòng)點(diǎn)循環(huán)內(nèi)部不能引入隨機(jī)決策否則循環(huán)可能永不終止因此混沌模式退而求其次地選擇已知最差解而非隨機(jī)解既保證確定性收斂又能覆蓋非最優(yōu)路徑。同文件還可見(jiàn)self.ctrl_plane.shuffled(domtree.children(block))用于打亂支配樹(shù)子節(jié)點(diǎn)的遍歷順序。此外cranelift/codegen/src/egraph/mod.rs 在化簡(jiǎn)規(guī)則產(chǎn)出多個(gè)候選后調(diào)用ctx.ctrl_plane.shuffle(optimized_values)打亂候選順序源碼注釋說(shuō)明返回順序本就無(wú)關(guān)緊要因此可以自由打亂以覆蓋更多取值路徑。5.3 機(jī)器碼發(fā)射擾動(dòng)分支優(yōu)化與指令布局cranelift/codegen/src/machinst/buffer.rs 的optimize_branches開(kāi)頭通過(guò)ctrl_plane.get_decision()決定是否跳過(guò)分支優(yōu)化pub fn optimize_branches(mut self, ctrl_plane: mut ControlPlane) { if ctrl_plane.get_decision() { return; } ... }同一文件的bind_label、emit_island等函數(shù)均接收mut ControlPlane使標(biāo)簽綁定、跳轉(zhuǎn)島island發(fā)射等布局決策也可被擾動(dòng)后端機(jī)器相關(guān)層同樣貫穿該類(lèi)型例如 cranelift/codegen/src/isa/aarch64/inst/emit.rs 的EmitState持有ctrl_plane并將其傳入bind_label/emit_islandx64、riscv64、s390x、pulley 等后端的 emit 模塊也都在使用列表之中塊布局方面cranelift/codegen/src/machinst/blockorder.rs 使用ctrl_plane.shuffled(block_succs[range].iter_mut().enumerate())打亂后繼基本塊的順序。這些落點(diǎn)共同覆蓋了啟發(fā)式優(yōu)化與機(jī)器碼布局兩大擾動(dòng)面與 README 描述的manipulate heuristic optimizations, clobber undefined register bits目標(biāo)吻合。5.4 差分測(cè)試混沌編譯結(jié)果必須與解釋器一致混沌模式的價(jià)值最終要靠正確性校驗(yàn)體現(xiàn)。在 fuzz/fuzz_targets/cranelift-fuzzgen.rs 中TestCase為每個(gè)函數(shù)維護(hù)一個(gè)ControlPlanepub struct TestCase { pub isa: isa::OwnedTargetIsa, pub functions: VecFunction, /// Control planes for function compilation. /// There should be an equal amount as functions to compile. pub ctrl_planes: VecControlPlane, ... }生成用例時(shí)通過(guò)ControlPlane::arbitrary(generator.u)?從 fuzz 輸入字節(jié)流構(gòu)造控制平面。隨后同一份函數(shù)分別走兩條路徑普通編譯 機(jī)器執(zhí)行混沌模式編譯 解釋器Interpreter執(zhí)行或反之。兩者結(jié)果必須逐位一致RunResult::Return(l)需逐元素bitwise_eq否則即視為發(fā)現(xiàn)缺陷。這正是混沌擾動(dòng)只能影響編譯路徑的選擇不能改變程序的語(yǔ)義這一不變量的機(jī)器校驗(yàn)。六、如何為 fuzz target 啟用混沌模式6.1 開(kāi)啟特性頂層 fuzz crate 在 fuzz/Cargo.toml 中提供了特性透?jìng)鱟haos [cranelift-control/chaos]因此啟用混沌模式只需在運(yùn)行 fuzz target 時(shí)攜帶--features chaoscargo fuzz run --features chaos cranelift-fuzzgen -- --fuel16--features chaos作用于 fuzz crate 及其依賴(lài)鏈--之后的部分是傳給 fuzz target 自身的參數(shù)。6.2 運(yùn)行與定位缺陷的完整流程復(fù)現(xiàn)在無(wú)混沌模式下先確認(rèn)同一用例可正常通過(guò)排除其他干擾放大以較大的 fuel 值如 64/128運(yùn)行確認(rèn)混沌模式能夠觸發(fā)缺陷收斂對(duì) fuel 值做二分32 - 16 - 8 ...直到找到觸發(fā)缺陷所需的最小擾動(dòng)次數(shù)定位結(jié)合ControlPlane在各決策點(diǎn)的調(diào)用順序egraph 的get_decision、buffer 的optimize_branches、blockorder 的shuffled等在最小 fuel 附近逐點(diǎn)排查即可鎖定具體是哪個(gè)擾動(dòng)決策暴露了編譯器缺陷。七、no_std 支持與使用邊界README 與 lib.rs 均強(qiáng)調(diào)了 crate 的no_std兼容性。當(dāng)前倉(cāng)庫(kù)中該 crate 以#![no_std]聲明編譯但存在明確邊界fuzz與chaos特性都依賴(lài)std因?yàn)橐蕾?lài)arbitrary并需要使用alloc::vec::Vec因此在no_std環(huán)境下不能通過(guò)arbitrary初始化ControlPlane也不能啟用混沌模式但ControlPlane其余常規(guī) APIdefault構(gòu)造、get_decision、get_arbitrary、shuffle、shuffled、set_fuel在no_std下依然可用此時(shí)自然只能得到空控制平面/默認(rèn)行為。這一設(shè)計(jì)使得依賴(lài)方如 cranelift/filetests、cranelift/jit、cranelift/module、cranelift/object 等 crate可以無(wú)條件地在代碼中引用ControlPlane而無(wú)需為嵌入式或 no_std 目標(biāo)做任何額外適配。八、總結(jié)cranelift-control是 Wasmtime/Cranelift 混沌模式的核心基礎(chǔ)設(shè)施其設(shè)計(jì)可以概括為三點(diǎn)零開(kāi)銷(xiāo)門(mén)控chaos特性關(guān)閉時(shí)編譯零大小 shim所有擾動(dòng)查詢(xún)內(nèi)聯(lián)為空操作發(fā)布構(gòu)建完全不受影響最小而完備的擾動(dòng)原語(yǔ)get_decision、get_arbitrary、shuffle/shuffled覆蓋了布爾決策、任意值采樣與順序擾動(dòng)三類(lèi)需求配合字節(jié)流驅(qū)動(dòng)的確定性使 fuzz 輸入可復(fù)現(xiàn)、可最小化燃料限制支撐缺陷定位--fuel參數(shù)配合set_fuel/consume_fuel讓混沌擾動(dòng)可以被量化控制從而對(duì)觸發(fā)缺陷的擾動(dòng)點(diǎn)做二分搜索。對(duì)于想要為 Cranelift 貢獻(xiàn)或深入理解其 fuzzing 體系的開(kāi)發(fā)者建議從 cranelift/control/src/lib.rs 的 crate 文檔入手再對(duì)照 cranelift/codegen/src/egraph/elaborate.rs 與 cranelift/codegen/src/machinst/buffer.rs 中的實(shí)際調(diào)用點(diǎn)最后用cargo fuzz run --features chaos cranelift-fuzzgen -- --fuelN親手體驗(yàn)這一套白盒擾動(dòng)與差分校驗(yàn)閉環(huán)?!久赓M(fèi)下載鏈接】wasmtimeA lightweight WebAssembly runtime that is fast, secure, and standards-compliant項(xiàng)目地址: https://gitcode.com/gh_mirrors/wa/wasmtime創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考