/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file LICENSE.rst or https://cmake.org/licensing for details. */ /* * Fuzzer for CMake script execution * * This fuzzer executes CMake scripts in script mode (-P). * This exercises the majority of CMake's codebase including: * - All built-in commands * - Variable expansion * - Control flow (if, foreach, while, function, macro) * - String/list/file operations * - Generator expressions * * This is the highest-impact fuzzer for coverage. * * Performance notes: * - Uses memfd_create on Linux for memory-backed file I/O * - Falls back to temp files on other platforms */ #include #include #include #include #include #include #include #include "cmCMakePolicyCommand.h" #include "cmExecutionStatus.h" #include "cmGlobalGenerator.h" #include "cmMakefile.h" #include "cmMessenger.h" #include "cmState.h" #include "cmStateSnapshot.h" #include "cmSystemTools.h" #include "cmake.h" #ifdef __linux__ # include # ifndef MFD_CLOEXEC # define MFD_CLOEXEC 0x0001U # endif #endif static constexpr size_t kMaxInputSize = 256 * 1024; static std::string g_testDir; static std::string g_scriptFile; static bool g_useMemfd = false; extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) { (void)argc; (void)argv; // Suppress output during fuzzing (set once at init) cmSystemTools::SetMessageCallback( [](std::string const&, cmMessageMetadata const&) {}); cmSystemTools::SetStdoutCallback([](std::string const&) {}); cmSystemTools::SetStderrCallback([](std::string const&) {}); // Create unique test directory (even with memfd, scripts can create files) char tmpl[] = "/tmp/cmake_fuzz_script_XXXXXX"; char* dir = mkdtemp(tmpl); if (dir) { g_testDir = dir; } else { g_testDir = "/tmp/cmake_fuzz_script"; cmSystemTools::MakeDirectory(g_testDir); } #ifdef __linux__ // Try to use memfd for better performance int fd = memfd_create("cmake_fuzz", MFD_CLOEXEC); if (fd >= 0) { g_useMemfd = true; // Create path via /proc/self/fd g_scriptFile = "/proc/self/fd/" + std::to_string(fd); // Keep fd open - will be reused } else #endif { g_scriptFile = g_testDir + "/fuzz_script.cmake"; } return 0; } extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) { if (size == 0 || size > kMaxInputSize) { return 0; } #ifdef __linux__ if (g_useMemfd) { // Extract fd from path and write directly int fd = std::atoi(g_scriptFile.c_str() + 14); // "/proc/self/fd/" ftruncate(fd, 0); lseek(fd, 0, SEEK_SET); if (write(fd, data, size) != static_cast(size)) { return 0; } } else #endif { // Write script to temp file FILE* fp = fopen(g_scriptFile.c_str(), "wb"); if (!fp) return 0; fwrite(data, 1, size, fp); fclose(fp); } // Save CWD in case script uses file(CHDIR) std::string cwd = cmSystemTools::GetCurrentWorkingDirectory(); // Create cmake instance for script mode cmake cm(cmState::Role::Script); cm.SetHomeDirectory(g_testDir); cm.SetHomeOutputDirectory(g_testDir); // Run the script std::vector args; args.push_back("cmake"); args.push_back("-P"); args.push_back(g_scriptFile); (void)cm.Run(args, false); // Restore CWD before cleanup (script may have changed it via file(CHDIR)) cmSystemTools::ChangeDirectory(cwd); // Cleanup temp file (memfd doesn't need cleanup) if (!g_useMemfd) { unlink(g_scriptFile.c_str()); } // Clean up any files the script may have created in g_testDir // This prevents disk growth and non-determinism from previous iterations cmSystemTools::RemoveADirectory(g_testDir); cmSystemTools::MakeDirectory(g_testDir); return 0; }