#include #include #include "fold/confirm/confirmation_task.hpp" using namespace fold; using namespace fold::confirm; TEST_CASE("confirmation task JSON round-trips source targets", "[confirmation]") { FindingConfirmationTask task; task.version = "1"; task.task_id = "confirm:bounded-symbolic:oob:file.cc:42:7"; task.finding_id = "oob-access:file.cc:42:7"; task.hazard = FindingKind::OOBAccess; task.backend = ConfirmationBackend::Source; task.lane = ConfirmationLane::Clang; task.adapter = ConfirmationAdapter::BoundedSymbolic; task.target.artifact = "/repo/file.cc"; task.target.function = "demo::parse"; task.target.source_location = Location{"/repo/file.cc", 42, 7, "demo::parse"}; task.input_channel = InputChannel::Harness; task.controlled_values = {"idx", "len"}; task.path_predicate = "idx >= len"; task.expected_violation = "out-of-bounds access"; task.artifacts.push_back({"graphml", "witness/file.graphml", "", "application/xml"}); task.fact_provenance = ir::FactProvenance{"source", "clang", "/repo/file.cc", "hash", ir::makeFunctionId("clang", "/repo/file.cc", "demo::parse"), "", "", ""}; const auto j = confirmationTaskToJson(task); REQUIRE(j[json_keys::kHazard] == "oob-access"); REQUIRE(j[json_keys::kTarget][json_keys::kSourceLocation][json_keys::kLine] == 42); const auto parsed = confirmationTaskFromJson(j); REQUIRE(parsed.task_id == task.task_id); REQUIRE(parsed.finding_id == task.finding_id); REQUIRE(parsed.hazard == FindingKind::OOBAccess); REQUIRE(parsed.backend == ConfirmationBackend::Source); REQUIRE(parsed.lane == ConfirmationLane::Clang); REQUIRE(parsed.adapter == ConfirmationAdapter::BoundedSymbolic); REQUIRE(parsed.target.source_location.has_value()); REQUIRE(parsed.target.source_location->file == "/repo/file.cc"); REQUIRE(parsed.target.source_location->line == 42); REQUIRE(parsed.input_channel == InputChannel::Harness); REQUIRE(parsed.controlled_values.size() == 2); REQUIRE(parsed.artifacts.size() == 1); REQUIRE(parsed.artifacts.front().media_type == "application/xml"); REQUIRE(parsed.fact_provenance.has_value()); REQUIRE(parsed.fact_provenance->lane == "clang"); } TEST_CASE("confirmation task JSON round-trips binary targets", "[confirmation]") { FindingConfirmationTask task; task.task_id = "confirm:angr:binary:401000"; task.finding_id = "binary-oob:app:0x401000"; task.hazard = FindingKind::OOBAccess; task.backend = ConfirmationBackend::Binary; task.lane = ConfirmationLane::GhidraPcode; task.adapter = ConfirmationAdapter::Angr; task.target.artifact = "/tmp/app"; task.target.function = "sym.vuln"; task.target.binary_address = 0x401000; task.input_channel = InputChannel::Stdin; task.partial = true; const auto parsed = confirmationTaskFromJson(confirmationTaskToJson(task)); REQUIRE(parsed.backend == ConfirmationBackend::Binary); REQUIRE(parsed.lane == ConfirmationLane::GhidraPcode); REQUIRE(parsed.adapter == ConfirmationAdapter::Angr); REQUIRE(parsed.target.binary_address.has_value()); REQUIRE(*parsed.target.binary_address == 0x401000); REQUIRE(!parsed.target.source_location.has_value()); REQUIRE(parsed.input_channel == InputChannel::Stdin); REQUIRE(parsed.partial); } TEST_CASE("confirmation result JSON round-trips ConcreteWitness evidence", "[confirmation]") { FindingConfirmationResult result; result.task_id = "confirm:libfuzzer-asan:oob:file.cc:42:7"; result.finding_id = "oob-access:file.cc:42:7"; result.status = ConfirmationStatus::Confirmed; result.adapter = ConfirmationAdapter::LibFuzzerAsan; result.promoted_confidence = Confidence::ConcreteWitness; result.progress.reached = true; result.progress.triggered = true; result.progress.detected = true; result.progress.replayed = true; result.progress.time_to_detected_ms = 17; result.message = "ASan observed heap-buffer-overflow at predicted sink"; result.fuzz_witness = FuzzWitness{"run/crash-1", "heap-buffer-overflow", "/repo/file.cc", 42}; result.artifacts.push_back({"seed", "run/crash-1", "abc123", "application/octet-stream"}); result.diagnostics.push_back("matched by finding id"); const auto j = confirmationResultToJson(result); REQUIRE(j[json_keys::kStatus] == "confirmed"); REQUIRE(j[json_keys::kPromotedConfidence] == "concrete-witness"); REQUIRE(j[json_keys::kFuzzWitness][json_keys::kAsanKind] == "heap-buffer-overflow"); const auto parsed = confirmationResultFromJson(j); REQUIRE(parsed.status == ConfirmationStatus::Confirmed); REQUIRE(parsed.adapter == ConfirmationAdapter::LibFuzzerAsan); REQUIRE(parsed.promoted_confidence.has_value()); REQUIRE(*parsed.promoted_confidence == Confidence::ConcreteWitness); REQUIRE(parsed.progress.replayed); REQUIRE(parsed.progress.time_to_detected_ms == 17); REQUIRE(parsed.fuzz_witness.has_value()); REQUIRE(parsed.fuzz_witness->seed_path == "run/crash-1"); REQUIRE(parsed.artifacts.size() == 1); REQUIRE(parsed.diagnostics.size() == 1); } TEST_CASE("confirmation progress rejects impossible stage ordering", "[confirmation]") { FindingConfirmationResult result; result.progress.detected = true; REQUIRE_THROWS_AS(confirmationResultToJson(result), std::invalid_argument); auto valid = confirmationResultToJson(FindingConfirmationResult{}); valid[json_keys::kProgress][json_keys::kReplayed] = true; REQUIRE_THROWS_AS(confirmationResultFromJson(valid), std::invalid_argument); } TEST_CASE("confirmation parsers reject unknown enum strings", "[confirmation]") { auto j = confirmationTaskToJson(FindingConfirmationTask{}); j[json_keys::kAdapter] = "not-an-adapter"; REQUIRE_THROWS_AS(confirmationTaskFromJson(j), std::invalid_argument); auto r = confirmationResultToJson(FindingConfirmationResult{}); r[json_keys::kStatus] = "maybe"; REQUIRE_THROWS_AS(confirmationResultFromJson(r), std::invalid_argument); } TEST_CASE("confirmation JSON omits absent optional result fields", "[confirmation]") { FindingConfirmationResult result; result.task_id = "task"; result.status = ConfirmationStatus::Unsupported; const auto j = confirmationResultToJson(result); REQUIRE(!j.contains(json_keys::kPromotedConfidence)); REQUIRE(!j.contains(json_keys::kFuzzWitness)); const auto parsed = confirmationResultFromJson(j); REQUIRE(parsed.status == ConfirmationStatus::Unsupported); REQUIRE(!parsed.promoted_confidence.has_value()); REQUIRE(!parsed.fuzz_witness.has_value()); } TEST_CASE("confirmation helpers map source findings into bounded-symbolic tasks", "[confirmation]") { Finding finding; finding.id = "null-deref:/repo/main.cpp:3:20"; finding.kind = FindingKind::NullDeref; finding.confidence = Confidence::Proved; finding.message = "null pointer dereference"; finding.locations.push_back(Location{"/repo/main.cpp", 3, 20, "f"}); finding.evidence.variable_name = "p"; finding.evidence.path_trace = {"branch_taken: /repo/main.cpp:3"}; auto task = confirmationTaskForFinding(finding, ConfirmationAdapter::BoundedSymbolic, ConfirmationBackend::Source, ConfirmationLane::Clang, InputChannel::Harness); REQUIRE(task.finding_id == finding.id); REQUIRE(task.hazard == FindingKind::NullDeref); REQUIRE(task.target.source_location.has_value()); REQUIRE(task.target.source_location->function == "f"); REQUIRE(task.controlled_values == std::vector{"p"}); REQUIRE(task.path_predicate.find("branch_taken") != std::string::npos); auto result = confirmationResultForFinding(finding, task, ConfirmationStatus::Confirmed); REQUIRE(result.task_id == task.task_id); REQUIRE(result.finding_id == finding.id); REQUIRE(result.status == ConfirmationStatus::Confirmed); REQUIRE(result.adapter == ConfirmationAdapter::BoundedSymbolic); REQUIRE(!result.promoted_confidence.has_value()); } TEST_CASE("confirmation helpers preserve ConcreteWitness promotion evidence", "[confirmation]") { Finding finding; finding.id = "oob-access:/repo/main.cpp:5:9"; finding.kind = FindingKind::OOBAccess; finding.confidence = Confidence::ConcreteWitness; finding.evidence.fuzz_witness = FuzzWitness{"crash.bin", "stack-buffer-overflow", "/repo/main.cpp", 5}; auto task = confirmationTaskForFinding(finding, ConfirmationAdapter::LibFuzzerAsan, ConfirmationBackend::Source, ConfirmationLane::Clang, InputChannel::Harness); auto result = confirmationResultForFinding(finding, task, ConfirmationStatus::Confirmed, "matched ASan crash"); REQUIRE(result.promoted_confidence.has_value()); REQUIRE(*result.promoted_confidence == Confidence::ConcreteWitness); REQUIRE(result.fuzz_witness.has_value()); REQUIRE(result.fuzz_witness->asan_kind == "stack-buffer-overflow"); }