/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file LICENSE.rst or https://cmake.org/licensing for details. */ /* * Fuzzer for CMake's file(LOCK) command * * The file(LOCK) command manages file locks for synchronization. * This fuzzer tests various lock scenarios and argument combinations. * * Coverage targets: * - Lock acquisition (LOCK) * - Lock release (RELEASE) * - Guard modes (FUNCTION, FILE, PROCESS) * - Timeout handling * - Error paths * * Security focus: * - Symlink handling (CVE for data destruction) * - Path traversal in lock paths * - Race conditions */ #include #include #include #include #include #include #include "cmFileLock.h" #include "cmFileLockResult.h" #include "cmSystemTools.h" // Limit input size static constexpr size_t kMaxInputSize = 4096; // Sandbox directory static std::string g_testDir; extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) { (void)argc; (void)argv; // Create unique test directory char tmpl[] = "/tmp/cmake_fuzz_lock_XXXXXX"; char* dir = mkdtemp(tmpl); if (dir) { g_testDir = dir; } else { g_testDir = "/tmp/cmake_fuzz_lock"; cmSystemTools::MakeDirectory(g_testDir); } return 0; } extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) { if (size < 1 || size > kMaxInputSize) { return 0; } // Use first byte for flags uint8_t flags = data[0]; // Create a test file with known content std::string testFile = g_testDir + "/lock_target.txt"; std::string lockFile = g_testDir + "/test.lock"; char const* testContent = "IMPORTANT DATA - MUST NOT BE TRUNCATED"; { FILE* fp = fopen(testFile.c_str(), "w"); if (!fp) return 0; fputs(testContent, fp); fclose(fp); } // Test different scenarios based on fuzz input cmFileLock lock; // Vary the lock file path based on remaining input std::string lockPath = lockFile; if (size > 1 && (flags & 0x01)) { // Use part of input as filename suffix (sanitized) size_t nameLen = std::min(size - 1, size_t(32)); std::string suffix; for (size_t i = 0; i < nameLen; ++i) { char c = static_cast(data[1 + i]); // Only allow safe filename characters if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-') { suffix += c; } } if (!suffix.empty()) { lockPath = g_testDir + "/" + suffix + ".lock"; } } // Test symlink scenario (security-critical) if (flags & 0x02) { // Create a symlink to the test file std::string symlinkPath = g_testDir + "/symlink.lock"; unlink(symlinkPath.c_str()); if (symlink(testFile.c_str(), symlinkPath.c_str()) == 0) { lockPath = symlinkPath; } } // Determine timeout - use 0 for fuzzing to avoid blocking // (non-zero timeouts would stall the fuzzer) unsigned long timeout = 0; // Try to acquire lock cmFileLockResult result = lock.Lock(lockPath, timeout); (void)result.IsOk(); // Always try to release (void)lock.Release(); // Security check: Verify test file wasn't truncated { FILE* fp = fopen(testFile.c_str(), "r"); if (fp) { char buffer[256] = { 0 }; size_t bytesRead = fread(buffer, 1, sizeof(buffer) - 1, fp); fclose(fp); if (bytesRead == 0 || strcmp(buffer, testContent) != 0) { // DATA DESTRUCTION DETECTED! fprintf(stderr, "VULNERABILITY: File was truncated or modified!\n"); fprintf(stderr, "Expected: '%s'\n", testContent); fprintf(stderr, "Got: '%s' (%zu bytes)\n", buffer, bytesRead); abort(); } } } // Cleanup unlink(lockPath.c_str()); unlink((g_testDir + "/symlink.lock").c_str()); return 0; }