
UT 기반 자동화 퍼즈 드라이버 생성
UTopia는 단위 테스트에서 퍼즈 드라이버를 자동으로 생성하는 도구입니다.
UTopia는 퍼저 작성에 대한 특별한 지식 없이도 개발자가 퍼즈 테스트를 수행할 수 있게 해줍니다. 퍼징에 익숙한 개발자도 퍼즈 드라이버를 자동으로 생성하여 상당한 시간을 절약할 수 있습니다.
UTopia는 GoogleTest, Boost.Test 또는 Tizen TCT로 단위 테스트를 작성한 C/C++ 라이브러리를 지원합니다.
UTopia가 발견한 버그를 보려면 트로피 페이지를 방문하세요. 또한 일부 UTopia 기반 퍼저도 확인할 수 있습니다.
손쉬운 설정을 위해 UTopia 실행용 docker 이미지를 제공합니다. docker 컨테이너 안에서 UTopia를 빌드하고 퍼저를 생성/실행할 수 있습니다.
아래 명령으로 docker 이미지를 빌드하세요.
docker buildx build -f docker/Dockerfile -t utopia . #llvm-10
docker buildx build --build-arg LLVM_VERSION=12 -f docker/Dockerfile -t utopia . #llvm-12
UTopia는 LLVM 버전 10에서 가장 잘 동작하도록 설계되었습니다. 또한 LLVM 버전 12에서 단위 테스트를 통과하는 것이 확인되었습니다. 따라서 LLVM 10을 권장하지만, 원한다면 LLVM 버전 12를 시도할 수 있습니다.
UTopia는 LLVM, Protobuf 및 GoogleTest에 의존합니다. 의존성을 수동으로 설치할 수도 있지만, 제공된 docker 이미지를 사용하는 것을 권장합니다.
저장소를 클론한 후 모든 하위 모듈을 초기화하고 업데이트하세요:
git submodule update --init --recursive
UTopia를 빌드하려면 아래 cmake 과정을 따르세요.
cd $UTOPIA_HOME_DIR
cmake -B build -S .
cmake --build build -j$(nproc)
일부 선정된 프로젝트의 경우 추가 작업 없이 헬퍼 스크립트를 사용하여 도구를 실행할 수 있습니다. helper/README.md를 참조하세요. 다른 프로젝트의 경우 아래 매뉴얼을 확인하세요.
Target Analyzer는 대상 라이브러리 코드를 분석하고 결과를 json 파일로 생성합니다. 필수 명령줄 옵션은 아래와 같습니다.
target_analyzer --db ${builddb_path} --extern ${extern_path} --public ${api_json_path} --out ${output_path}
Build db는 대상 라이브러리 코드의 AST 및 IR 파일 경로를 포함하는 json 파일입니다. 형식은 아래와 같습니다.
{
"bc": "/root/fuzz-test-generation/exp/sample/output/bc/libcommon.a.bc",
"ast": [
"/root/fuzz-test-generation/exp/sample/libcommon.a_ast/codec/common/src/ast1.o.ast",
"/root/fuzz-test-generation/exp/sample/libcommon.a_ast/codec/common/src/ast2.o.ast"
],
"project_dir": "/root/fuzz-test-generation/exp/sample"
}
특정 라이브러리의 LLVM bitcode 파일 경로는 "bc" 키워드를 사용하여 지정해야 합니다.
현재는 bitcode 파일 하나만 허용하므로, llvm-link를 사용하여 여러 bitcode 파일을
하나의 bitcode 파일로 연결할 수 있습니다.
특정 라이브러리의 AST 파일 경로는 "ast" 키워드를 사용하여 지정해야 합니다. 지정된 bitcode 파일과 ast 파일은 특정 라이브러리에 대해 동일한 소스 코드에서 생성된 것임을 유의하세요.
Target Analyzer는 정확한 결과를 위해 다른 라이브러리의 target analyzer 리포트를 입력으로 받습니다. 이 경로는 다른 리포트가 저장된 디렉터리 경로여야 하며, 즉 여러 라이브러리 리포트가 허용됩니다.
분석할 API 함수 이름입니다. 아래와 같은 형식의 json 파일이어야 합니다.
{
"libcommon.a": [
"API1",
"API2",
"API3"
]
}
아래 명령을 사용하여 특정 라이브러리의 API 목록을 얻을 수 있습니다.
nm --no-demangle --defined-only -g ${librarypath} | awk '$2=="T" {k=""; for(i=3;i<=NF;i++) k=k $i""; print k}'
Direction 속성은 target analyzer가 사용하는 핵심 파라미터입니다. 함수 내에서 파라미터가 읽기(Dir_In), 쓰기(Dir_Out), 또는 읽기와 쓰기 모두(Dir_In | Dir_Out)에 사용되는지 나타냅니다. target analyzer는 이 속성을 다음 요소로 구성된 열거형을 통해 정의합니다:
enum Dir {
Dir_NoOp = 0x000, // No operation
Dir_In = 0x100, // Input direction
Dir_Out = 0x010, // Output direction
Dir_Unidentified = 0x001 // Unidentified direction
};
예를 들어, 제공된 JSON 파일 조각에서:
{
"Direction": {
"BF_crypt(0)": 256,
"BF_crypt(1)": 272,
"BF_crypt(2)": 272,
"BF_decode(1)": 272
}
}
이 표기법은 함수 내 각 파라미터가 입력, 출력 또는 둘 모두에 어떻게 사용되는지 이해하는 데 도움이 되며, 함수가 수행하는 데이터 흐름과 작업에 대한 명확한 통찰력을 제공합니다.
UT Analyzer는 대상 라이브러리의 단위 테스트 코드를 분석하고 결과를 json 파일로 생성합니다. 필수 명령줄 옵션은 아래와 같습니다.
ut_analyzer --entry ${entry_path} --extern ${extern_path} --ut ${ut_type} --name ${lib_name} --public ${api_json_path} --out ${output_path}
대부분의 옵션은 target_analyzer와 동일합니다. entry_path는 라이브러리가 아닌 단위 테스트 실행 파일의 AST/IR 파일을 지정해야 합니다.
대상 프로젝트에서 사용하는 프레임워크로, tct, gtest 또는 boost일 수 있습니다.
fuzz_generator는 target_anlayzer 및 ut_analyzer의 리포트 파일을 사용하여 퍼즈 드라이버를 생성합니다. 필수 명령줄 옵션은 아래와 같습니다.
fuzz_generator --src ${src_path} --target ${target_analyzer_report_path} --ut ${ut_analyzer_report_path} --public ${api_json_path} --out ${output_dir}
src_path는 단위 테스트 소스 코드가 저장된 디렉터리 경로입니다. fuzz_generator는 이 디렉터리를 복사하고 복사된 파일을 수정하여 퍼즈 드라이버를 생성합니다.
다른 옵션은 target_analyzer 옵션과 동일합니다.
이 섹션에서는 생성된 퍼즈 드라이버의 기능과 구현 세부 사항을 설명합니다. 이는 퍼즈 테스트가 대상 소스 코드와 어떻게 통합되는지 이해하는 데 중요합니다.
fuzz_entry.cc (자동 생성 파일)
DEFINE_PROTO_FUZZER(const AutoFuzz::FuzzArgsProfile &autofuzz_mutation) {
... /* Values are assigned from autofuzz_mutation */
enterAutofuzz();
}
이 함수는 생성된 퍼즈 드라이버의 진입점 역할을 합니다. 퍼저가 생성한 값을 가져와 변수에 할당하며, 이 변수들은 이후 라이브러리 함수를 호출하는 데 사용됩니다. 마지막으로 함수는 **enterAutofuzz();**를 호출하여 퍼즈 테스트 과정을 진행합니다.
대상 테스트 케이스를 정의하는 소스 코드
#ifdef __cplusplus
extern "C" {
#endif
void enterAutofuzz() {
class AutofuzzTest : public ::Parser_TestArray_Test {
public:
void runTest() {
try {
SetUpTestCase();
} catch (std::exception &E) {}
try {
SetUp();
} catch (std::exception &E) {}
try {
TestBody();
} catch (std::exception &E) {}
try {
TearDown();
} catch (std::exception &E) {}
try {
TearDownTestCase();
} catch (std::exception &E) {}
}
};
AutofuzzTest Fuzzer;
Fuzzer.runTest();
}
#ifdef __cplusplus
}
#endif
대상 테스트 케이스를 정의하는 소스 코드의 마지막 부분에서 UTopia는 enterAutofuzz 함수를 주입합니다. 이 함수 내부에는 ::Parser_TestArray_Test를 상속하는 AutofuzzTest 클래스가 선언됩니다. 이 부모 클래스는 GoogleTest 프레임워크에 의해 정의되며 아래와 같이 다루는 테스트 케이스에 특화되어 있습니다:
TEST(Parser, TestArray)
{
...
}
runTest() 메서드는 GoogleTest에 독립적으로 테스트 케이스를 실행하며, GoogleTest가 각 테스트 케이스에 대해 일반적으로 호출하는 다섯 가지 함수를 호출합니다. 이 방식을 통해 GoogleTest 프레임워크에 의존하지 않고 테스트를 직접 실행할 수 있습니다.
퍼즈 드라이버는 fuzz_generator에 명령줄 옵션으로 전달된 ${output_dir}에 생성됩니다.
단위 테스트 실행 파일과 동일한 컴파일러 명령을 사용하여 빌드할 수 있습니다.
fuzz_generator가 생성한 fuzz_entry.cc, FuzzArgsProto.pb.cc 파일을 포함해야 합니다.
해당 파일은 ${output_dir}에서 찾을 수 있습니다.
python3 -m helper.make {library name}
python3 -m helper.build {library name}
'UTopia' 전체 파이프라인의 모든 출력물은 다음 두 디렉터리에서 확인할 수 있습니다:
exp/{library name}/output,result/test/{library name}