#include #include #include #include #include #include #include #include #include #include #include #include "fold/detect/cross_tu_cache.hpp" #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++20"}, "input.cpp"); } std::vector runTaint(const std::string& code) { fold::detect::CrossTUCache::instance().clear(); auto unit = parse(code); REQUIRE(unit); fold::SolverBudget b; fold::SolverStats s; return fold::detectTaintTrackInContext(unit->getASTContext(), b, s); } std::pair, std::string> runTaintWithLog(const std::string& code) { std::ostringstream log; auto previous_logger = spdlog::default_logger(); auto sink = std::make_shared(log); spdlog::set_default_logger(std::make_shared("taint-track-test", sink)); auto findings = runTaint(code); spdlog::set_default_logger(previous_logger); return {findings, log.str()}; } std::vector runSql(const std::string& code) { fold::detect::CrossTUCache::instance().clear(); auto unit = parse(code); REQUIRE(unit); fold::SolverBudget b; fold::SolverStats s; return fold::detectSqlInjectionInContext(unit->getASTContext(), b, s); } bool hasKind(const std::vector& findings, fold::FindingKind kind) { for (const auto& finding : findings) { if (finding.kind == kind) return true; } return false; } } // namespace TEST_CASE("taint-track: tainted path into open() fires", "[taint-track][path]") { auto findings = runTaint("extern \"C\" char* getenv(const char*);\n" "extern \"C\" int open(const char*, int, ...);\n" "int f() {\n" " char* path = getenv(\"X\");\n" " return open(path, 0);\n" "}\n"); REQUIRE(hasKind(findings, fold::FindingKind::TaintTrack)); } TEST_CASE("sql-injection: sqlite3_bind_text marks a value sanitized", "[taint-track][sql-bind]") { const std::string code = "struct sqlite3; struct sqlite3_stmt;\n" "extern \"C\" char* getenv(const char*);\n" "extern \"C\" int sqlite3_bind_text(sqlite3_stmt*, int, const char*, int, void*);\n" "extern \"C\" int sqlite3_exec(sqlite3*, const char*, int (*)(void*, int, char**, char**), " "void*, char**);\n" "void f(sqlite3* db, sqlite3_stmt* stmt) {\n" " char* user = getenv(\"X\");\n" " sqlite3_bind_text(stmt, 1, user, -1, nullptr);\n" " sqlite3_exec(db, user, nullptr, nullptr, nullptr);\n" "}\n"; REQUIRE_FALSE(hasKind(runSql(code), fold::FindingKind::SqlInjection)); REQUIRE_FALSE(hasKind(runTaint(code), fold::FindingKind::TaintTrack)); } TEST_CASE("taint-track: incompatible sink overload fails closed", "[taint-track][model]") { auto findings = runTaint("int mysql_query();\n" "void f() { (void)mysql_query(); }\n"); REQUIRE_FALSE(hasKind(findings, fold::FindingKind::TaintTrack)); } TEST_CASE("taint-track: executeAsync is not an exec sink", "[taint-track][model]") { const auto [findings, log] = runTaintWithLog("struct Command { void executeAsync(); };\n" "void f(Command& command) { command.executeAsync(); }\n"); REQUIRE_FALSE(hasKind(findings, fold::FindingKind::TaintTrack)); REQUIRE(log.find("rejected sink model for executeAsync") == std::string::npos); } TEST_CASE("taint-track: tainted argv into execve() fires", "[taint-track][command]") { auto findings = runTaint("extern \"C\" char* getenv(const char*);\n" "extern \"C\" int execve(const char*, char* const[], char* const[]);\n" "int f(char* argv[]) {\n" " char* path = getenv(\"SHELL\");\n" " return execve(path, argv, nullptr);\n" "}\n"); REQUIRE(hasKind(findings, fold::FindingKind::TaintTrack)); } TEST_CASE("taint-track: tainted argv into posix_spawnp() fires", "[taint-track][command]") { auto findings = runTaint("typedef int pid_t;\n" "extern \"C\" int posix_spawnp(pid_t*, const char*, const void*, const void*, " "char* const[], char* const[]);\n" "int f(char* argv[]) {\n" " pid_t pid = 0;\n" " return posix_spawnp(&pid, \"/bin/sh\", nullptr, nullptr, argv, nullptr);\n" "}\n"); REQUIRE(hasKind(findings, fold::FindingKind::TaintTrack)); }