#include #include #include "fold/fuzz/asan_parser.hpp" using fold::fuzz::parseAsanReport; namespace { constexpr const char* kHeapBufferOverflow = R"(==12345==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000010 READ of size 4 at 0x602000000010 thread T0 #0 0x100abc in oobtest /tmp/oob.c:3:19 #1 0x100def in LLVMFuzzerTestOneInput harness.cc:14:5 SUMMARY: AddressSanitizer: heap-buffer-overflow /tmp/oob.c:3:19 in oobtest )"; constexpr const char* kSegv = R"(AddressSanitizer:DEADLYSIGNAL ================================================================= ==99==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 The signal is caused by a READ memory access. Hint: address points to the zero page. #0 0x7f leaky /tmp/null.c:5:16 SUMMARY: AddressSanitizer: SEGV /tmp/null.c:5:16 in leaky )"; constexpr const char* kUbsanOverflow = R"(/tmp/ov.c:8:14: runtime error: signed integer overflow: ... SUMMARY: UndefinedBehaviorSanitizer: signed-integer-overflow /tmp/ov.c:8:14 )"; constexpr const char* kRealUbsanDivision = R"(/tmp/div.c:7:16: runtime error: division by zero SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /tmp/div.c:7:16 in divide )"; constexpr const char* kRealUbsanOverflow = R"(/tmp/ov.c:8:14: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int' SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /tmp/ov.c:8:14 in add )"; constexpr const char* kCleanRun = "INFO: libFuzzer ran the input but no crash.\n"; constexpr const char* kSummaryWithoutColumn = "SUMMARY: AddressSanitizer: heap-use-after-free src/foo.c:42 in worker\n"; } // namespace TEST_CASE("parseAsanReport extracts heap-buffer-overflow + file:line:col", "[fuzz][asan]") { auto r = parseAsanReport(kHeapBufferOverflow); REQUIRE(r.has_value()); REQUIRE(r->valid()); REQUIRE(r->kind == "heap-buffer-overflow"); REQUIRE(r->file == "/tmp/oob.c"); REQUIRE(r->line == 3); REQUIRE(r->column == 19); REQUIRE(r->function == "oobtest"); } TEST_CASE("parseAsanReport extracts SEGV from deadly-signal block", "[fuzz][asan]") { auto r = parseAsanReport(kSegv); REQUIRE(r.has_value()); REQUIRE(r->kind == "SEGV"); REQUIRE(r->file == "/tmp/null.c"); REQUIRE(r->line == 5); REQUIRE(r->column == 16); REQUIRE(r->function == "leaky"); } TEST_CASE("parseAsanReport handles UBSan signed-integer-overflow", "[fuzz][asan]") { auto r = parseAsanReport(kUbsanOverflow); REQUIRE(r.has_value()); REQUIRE(r->kind == "signed-integer-overflow"); REQUIRE(r->file == "/tmp/ov.c"); REQUIRE(r->line == 8); REQUIRE(r->column == 14); // UBSan summary doesn't include an `in ` clause. REQUIRE(r->function.empty()); } TEST_CASE("parseAsanReport recovers division class from a generic UBSan summary", "[fuzz][ubsan]") { auto r = parseAsanReport(kRealUbsanDivision); REQUIRE(r.has_value()); REQUIRE(r->kind == "division-by-zero"); REQUIRE(r->file == "/tmp/div.c"); REQUIRE(r->line == 7); REQUIRE(r->column == 16); REQUIRE(r->function == "divide"); } TEST_CASE("parseAsanReport recovers overflow class from a generic UBSan summary", "[fuzz][ubsan]") { auto r = parseAsanReport(kRealUbsanOverflow); REQUIRE(r.has_value()); REQUIRE(r->kind == "signed-integer-overflow"); REQUIRE(r->file == "/tmp/ov.c"); REQUIRE(r->line == 8); REQUIRE(r->column == 14); REQUIRE(r->function == "add"); } TEST_CASE("parseAsanReport returns nullopt on clean output", "[fuzz][asan]") { REQUIRE_FALSE(parseAsanReport(kCleanRun).has_value()); } TEST_CASE("parseAsanReport accepts summary lines without a column", "[fuzz][asan]") { auto r = parseAsanReport(kSummaryWithoutColumn); REQUIRE(r.has_value()); REQUIRE(r->kind == "heap-use-after-free"); REQUIRE(r->file == "src/foo.c"); REQUIRE(r->line == 42); REQUIRE(r->column == 0); REQUIRE(r->function == "worker"); } TEST_CASE("parseAsanReport returns nullopt on empty input", "[fuzz][asan]") { REQUIRE_FALSE(parseAsanReport("").has_value()); }