mirror of
https://github.com/Kitware/CMake.git
synced 2026-06-24 16:58:07 +00:00
148 lines
3.7 KiB
C++
148 lines
3.7 KiB
C++
/* 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;
|
|
}
|