blob: 33d8d1e1bd5a50e6de70bf78c3facb774908695b [file] [log] [blame] [edit]
/* 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 <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <string>
#include <unistd.h>
#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<char>(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;
}