fold/tests/unit/test_sql_injection.cpp
trvon 86b2f14a43
feat(analysis): harden project-wide scans
- 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
2026-07-30 18:18:04 -04:00

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));
}