| /* 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 <cstddef> |
| #include <cstdint> |
| #include <cstdio> |
| #include <cstdlib> |
| #include <string> |
| #include <vector> |
| |
| #include <unistd.h> |
| |
| #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 <sys/mman.h> |
| # 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<ssize_t>(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<std::string> 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; |
| } |