編寫模糊測試器

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

模糊測試的程式碼範例

在下列每個範例中,假設您要測試的程式碼如下:

C/C++

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

荒漠油廠

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

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

簡單的模糊測試目標函式

對於每種語言,模糊測試目標函式都會使用提供的位元組,呼叫您要模糊測試的程式碼。如果模糊測試的介面有參數的記錄限制,您可以拒絕不符合這些限制的輸入內容。您也可以忽略傳回的錯誤,因為針對無效參數正常失敗是正確的行為。

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,建議使用下一節討論的 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;
  }

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

  • 首先,這項功能可讓您更輕鬆地快速編寫模糊測試器。
  • 其次,這項工具的設計目的是動態分割輸入內容,讓模糊測試器能根據涵蓋範圍資料有效率地建立新輸入內容。

這項技術有一項明顯缺點:

  • 由於輸入內容是動態分割,因此更難提供現有的語料庫。但仍可提供字典

荒漠油廠

您可以建立模糊測試目標函式,使用 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 建構新建立的模糊測試器。