- persist TU-owned graphs and guarded configuration evidence - add closed-world production reachability classification - calibrate detector precision and reviewed self-scan policy - terminate poisoned workers instead of longjmp recovery
88 lines
3.1 KiB
C++
88 lines
3.1 KiB
C++
#include <clang/Tooling/Tooling.h>
|
|
|
|
#include <catch2/catch_test_macros.hpp>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "fold/detect/cross_tu_cache.hpp"
|
|
#include "fold/detect/sql_injection.hpp"
|
|
#include "fold/report/finding.hpp"
|
|
#include "fold/symbolic/budget.hpp"
|
|
|
|
namespace {
|
|
|
|
struct TestResult {
|
|
std::vector<fold::Finding> findings;
|
|
fold::SolverStats stats;
|
|
};
|
|
|
|
std::unique_ptr<clang::ASTUnit> parse(const std::string& code) {
|
|
return clang::tooling::buildASTFromCodeWithArgs(code, {"-std=c++20"}, "input.cpp");
|
|
}
|
|
|
|
TestResult run(const std::string& code) {
|
|
fold::detect::CrossTUCache::instance().clear();
|
|
auto unit = parse(code);
|
|
REQUIRE(unit);
|
|
fold::SolverBudget b;
|
|
fold::SolverStats s;
|
|
auto findings = fold::detectSqlInjectionInContext(unit->getASTContext(), b, s);
|
|
return {std::move(findings), s};
|
|
}
|
|
|
|
bool hasSqlInjection(const std::vector<fold::Finding>& findings) {
|
|
for (const auto& f : findings) {
|
|
if (f.kind == fold::FindingKind::SqlInjection)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("sql-injection: getenv return assigned to query reaches DB sink", "[sql][getenv]") {
|
|
auto [findings, stats] = run("extern \"C\" char* getenv(const char*);\n"
|
|
"struct DB { void execute(const char*); };\n"
|
|
"extern DB db;\n"
|
|
"void f() {\n"
|
|
" const char* q = getenv(\"QUERY_STRING\");\n"
|
|
" db.execute(q);\n"
|
|
"}\n");
|
|
(void)stats;
|
|
REQUIRE(hasSqlInjection(findings));
|
|
REQUIRE(findings.size() == 1);
|
|
CHECK(findings.front().confidence == fold::Confidence::Heuristic);
|
|
CHECK_FALSE(findings.front().evidence.latent);
|
|
}
|
|
|
|
TEST_CASE("sql-injection: direct tainted parameter still reaches DB sink", "[sql][param]") {
|
|
auto [findings, stats] = run("struct DB { void execute(const char*); };\n"
|
|
"extern DB db;\n"
|
|
"void f(const char* user_query) {\n"
|
|
" db.execute(user_query);\n"
|
|
"}\n");
|
|
(void)stats;
|
|
REQUIRE(hasSqlInjection(findings));
|
|
REQUIRE(findings.size() == 1);
|
|
CHECK(findings.front().confidence == fold::Confidence::Latent);
|
|
CHECK(findings.front().evidence.latent);
|
|
CHECK(findings.front().evidence.precondition_required);
|
|
}
|
|
|
|
TEST_CASE("sql-injection: incompatible sink overload fails closed", "[sql][model]") {
|
|
auto [findings, stats] = run("int PQexec();\n"
|
|
"void f() { (void)PQexec(); }\n");
|
|
(void)stats;
|
|
REQUIRE_FALSE(hasSqlInjection(findings));
|
|
}
|
|
|
|
TEST_CASE("sql-injection: hardcoded query is safe", "[sql][safe]") {
|
|
auto [findings, stats] = run("struct DB { void execute(const char*); };\n"
|
|
"extern DB db;\n"
|
|
"void f() {\n"
|
|
" db.execute(\"select 1\");\n"
|
|
"}\n");
|
|
(void)stats;
|
|
REQUIRE_FALSE(hasSqlInjection(findings));
|
|
}
|