fold/tests/unit/test_taint_track.cpp
2026-07-29 23:22:00 -04:00

129 lines
5 KiB
C++

#include <clang/Frontend/ASTUnit.h>
#include <clang/Tooling/Tooling.h>
#include <spdlog/logger.h>
#include <spdlog/sinks/ostream_sink.h>
#include <spdlog/spdlog.h>
#include <catch2/catch_test_macros.hpp>
#include <memory>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
#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<clang::ASTUnit> parse(const std::string& code) {
return clang::tooling::buildASTFromCodeWithArgs(code, {"-std=c++20"}, "input.cpp");
}
std::vector<fold::Finding> 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::vector<fold::Finding>, std::string> runTaintWithLog(const std::string& code) {
std::ostringstream log;
auto previous_logger = spdlog::default_logger();
auto sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(log);
spdlog::set_default_logger(std::make_shared<spdlog::logger>("taint-track-test", sink));
auto findings = runTaint(code);
spdlog::set_default_logger(previous_logger);
return {findings, log.str()};
}
std::vector<fold::Finding> 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<fold::Finding>& 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));
}