// Phase 1 (sanitizer recognition): a value escaped/validated for a domain must // NOT trip a sink in that domain, but MUST still trip a sink in a different // domain (no cross-domain masking). #include #include #include #include #include #include #include "fold/detect/sql_injection.hpp" #include "fold/detect/taint_track.hpp" #include "fold/report/finding.hpp" #include "fold/symbolic/budget.hpp" namespace { std::unique_ptr parse(const std::string& code) { return clang::tooling::buildASTFromCodeWithArgs(code, {"-std=c++17"}, "input.cpp"); } std::size_t countKind(const std::vector& fs, fold::FindingKind k) { std::size_t n = 0; for (const auto& f : fs) if (f.kind == k) ++n; return n; } std::size_t runTaint(const std::string& code) { auto unit = parse(code); REQUIRE(unit); fold::SolverBudget b; fold::SolverStats s; auto fs = fold::detectTaintTrackInContext(unit->getASTContext(), b, s); return countKind(fs, fold::FindingKind::TaintTrack); } std::size_t runSql(const std::string& code) { auto unit = parse(code); REQUIRE(unit); fold::SolverBudget b; fold::SolverStats s; auto fs = fold::detectSqlInjectionInContext(unit->getASTContext(), b, s); return countKind(fs, fold::FindingKind::SqlInjection); } // Minimal prototypes so the fixtures parse as C++. constexpr const char* kDecls = "typedef struct MYSQL MYSQL; typedef struct PGconn PGconn;\n" "extern \"C\" char* getenv(const char*);\n" "extern \"C\" int system(const char*);\n" "extern \"C\" int mysql_query(MYSQL*, const char*);\n" "extern \"C\" unsigned long mysql_real_escape_string(MYSQL*, char*, const char*, unsigned " "long);\n" "extern \"C\" char* PQescapeLiteral(PGconn*, const char*, unsigned long);\n"; } // namespace TEST_CASE("taint: unsanitized env input into system() fires", "[taint][sanitizer]") { const std::string code = std::string(kDecls) + "void f(){ char* u = getenv(\"X\"); system(u); }"; REQUIRE(runTaint(code) >= 1); } TEST_CASE("sql: unsanitized env input into mysql_query fires", "[taint][sanitizer]") { const std::string code = std::string(kDecls) + "void f(MYSQL* db){ char* u = getenv(\"X\"); mysql_query(db, u); }"; REQUIRE(runSql(code) >= 1); } TEST_CASE("sql: PQescapeLiteral output is not flagged", "[taint][sanitizer]") { const std::string code = std::string(kDecls) + "void f(MYSQL* db, PGconn* c){ char* u = getenv(\"X\");" " char* q = PQescapeLiteral(c, u, 0); mysql_query(db, q); }"; REQUIRE(runSql(code) == 0); REQUIRE(runTaint(code) == 0); } TEST_CASE("sql: mysql_real_escape_string destination is not flagged", "[taint][sanitizer]") { const std::string code = std::string(kDecls) + "void f(MYSQL* db){ char* u = getenv(\"X\"); char buf[256];" " mysql_real_escape_string(db, buf, u, 255); mysql_query(db, buf); }"; REQUIRE(runSql(code) == 0); } TEST_CASE("cross-domain: SQL-escaped value into system() STILL fires", "[taint][sanitizer]") { // PQescapeLiteral sanitizes for SQL, not for the shell — a command sink must // still flag it. This guards against unsound cross-domain masking. const std::string code = std::string(kDecls) + "void f(PGconn* c){ char* u = getenv(\"X\");" " char* q = PQescapeLiteral(c, u, 0); system(q); }"; REQUIRE(runTaint(code) >= 1); }