1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
|
/* 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;
}
|