撰寫模糊效果

Fuchsia 的工具鍊支援使用 LLVM 的 libFuzzer 執行模糊作業。如要為特定介面建立模糊化工具,您必須實作「模糊目標函式」,這個函式會使用所提供的位元組序列練習介面。位元組序列稱為模糊的「輸入」。libFuzzer 會使用模糊目標函式搜尋導致恐慌或其他錯誤的輸入內容。

Fuzz 程式碼範例

針對下列每個範例,假設您要測試程式碼,如下所示:

C/C++

class Parser {
  Parser(const std::string &name, uint32_t flags);
  virtual ~Parser();
  int Parse(const uint8 *buf, size_t len);
};

Rust

struct ToyStruct {
    n: u8,
    s: String,
}

fn toy_example(input: ToyStruct) -> Result<u8, &'static str>;

簡單的 Fuzz 目標函式

針對各種語言,模糊目標函式將使用提供的位元組呼叫要進行模糊化的程式碼。如果正在模糊化的介面已記錄了參數的限制,您可以拒絕不符合這些限制條件的輸入。您也可以忽略傳回的錯誤,因為對無效參數執行失敗是正確的行為。

C/C++

如果是 C 和 C++,模糊目標函式必須包含 extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) 簽名並傳回 0:

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
  Parser parser("", 0);
  if (size < 5) {
    return 0;
  }
  parser.Parse(data, size);
  return 0;
}

請將此程式碼放在受到模糊化程式碼的來源檔案附近,與單元測試一樣。舉例來說,上述程式碼可能位於 parser-fuzztest.cc

Rust

針對 Rust,建議您使用下一節討論的 Arbitrary 特徵。

您也可以建立「手動」模糊目標函式,用於其他語言的簡單模糊目標函式。這個函式必須參照位元組切片做為單一參數、不傳回任何內容,且具有 #[fuzz] 屬性:

use fuzz::fuzz;

#[fuzz]
fn toy_example_u8(input: &[u8]) {
    if input.len() == 0 {
        return
    }
    let n = input[0];
    if let Ok(s) = std::str::from_utf8(input[1:]) {
        let _ = toy_example(ToyStruct{n, s: s.to_string(),});
    }
}

與單元測試一樣,此程式碼可以和其測試的程式碼放在同一個檔案中。舉例來說,上述程式碼可能位於 toy_example/src/lib.rs

支援更複雜的類型

每種語言都有一些公用程式,可幫助您建立更複雜的模糊目標函式:

C/C++

LLVM 提供的 FuzzedDataProvider 類別可協助您將提供的 data 的部分對應至更複雜的類型。

例如:

  #include <fuzzer/FuzzedDataProvider.h>

  extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    FuzzedDataProvider provider(data, size);
    auto flags = provider.ConsumeIntegral<uint32_t>();
    auto name = provider.ConsumeRandomLengthString();
    Parser parser(name, flags);
    auto buf = provider.ConsumeRemainingBytes<uint8_t>();
    parser.Parse(buf.data(), buf.size());
    return 0;
  }

使用這個程式庫有兩項明顯的優點:

  • 第一,這樣比較容易快速寫出模糊字元。
  • 其次,其設計能以動態方式分割輸入,讓模糊程序能有效從涵蓋率資料建立新的輸入。

有一個明顯的缺點:

  • 由於輸入會以動態方式分割,因此更難以提供既有的 corpus。不過,您還是可以提供字典

Rust

您可以建立模糊目標函式,以使用 arbitrary Crate 中的 Arbitrary 特徵擷取一或多個輸入內容。這是我們建議的方法。

如何編寫容易自動轉換任意輸入內容的模糊目標函式:

  1. 如有需要,請針對測試程式碼使用的類型實作 Arbitrary 特徵。如果可以,建議透過自動衍生特徵來達到這個目的。或者,您也可以按照 crate 的操作說明「手動」完成這項作業。

    例如,在您的 src/lib.rs 中:

    use arbitrary:Arbitrary;
    
    #[derive(Arbitrary)]
    struct ToyStruct { ... }
    
  2. 使用 #[fuzz] 屬性建立函式,將必要參數傳送至要測試的程式碼。

    例如,在您的 src/lib.rs 中:

    use fuzz::fuzz;
    
    #[fuzz]
    fn toy_example_arbitrary(input: ToyStruct) {
        let _ = toy_example(input);
    }
    

接下來,您可以使用 GN 和 Ninja 建構新建立的模糊作業。