Coverage Report

Created: 2026-02-09 06:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Tests/Fuzzing/cmFileLockFuzzer.cxx
Line
Count
Source
1
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2
   file LICENSE.rst or https://cmake.org/licensing for details.  */
3
4
/*
5
 * Fuzzer for CMake's file(LOCK) command
6
 *
7
 * The file(LOCK) command manages file locks for synchronization.
8
 * This fuzzer tests various lock scenarios and argument combinations.
9
 *
10
 * Coverage targets:
11
 * - Lock acquisition (LOCK)
12
 * - Lock release (RELEASE)
13
 * - Guard modes (FUNCTION, FILE, PROCESS)
14
 * - Timeout handling
15
 * - Error paths
16
 *
17
 * Security focus:
18
 * - Symlink handling (CVE for data destruction)
19
 * - Path traversal in lock paths
20
 * - Race conditions
21
 */
22
23
#include <cstddef>
24
#include <cstdint>
25
#include <cstdio>
26
#include <cstring>
27
#include <string>
28
29
#include <unistd.h>
30
31
#include "cmFileLock.h"
32
#include "cmFileLockResult.h"
33
#include "cmSystemTools.h"
34
35
// Limit input size
36
static constexpr size_t kMaxInputSize = 4096;
37
38
// Sandbox directory
39
static std::string g_testDir;
40
41
extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv)
42
6
{
43
6
  (void)argc;
44
6
  (void)argv;
45
46
  // Create unique test directory
47
6
  char tmpl[] = "/tmp/cmake_fuzz_lock_XXXXXX";
48
6
  char* dir = mkdtemp(tmpl);
49
6
  if (dir) {
50
6
    g_testDir = dir;
51
6
  } else {
52
0
    g_testDir = "/tmp/cmake_fuzz_lock";
53
0
    cmSystemTools::MakeDirectory(g_testDir);
54
0
  }
55
56
6
  return 0;
57
6
}
58
59
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
60
152
{
61
152
  if (size < 1 || size > kMaxInputSize) {
62
18
    return 0;
63
18
  }
64
65
  // Use first byte for flags
66
134
  uint8_t flags = data[0];
67
68
  // Create a test file with known content
69
134
  std::string testFile = g_testDir + "/lock_target.txt";
70
134
  std::string lockFile = g_testDir + "/test.lock";
71
134
  char const* testContent = "IMPORTANT DATA - MUST NOT BE TRUNCATED";
72
73
134
  {
74
134
    FILE* fp = fopen(testFile.c_str(), "w");
75
134
    if (!fp)
76
0
      return 0;
77
134
    fputs(testContent, fp);
78
134
    fclose(fp);
79
134
  }
80
81
  // Test different scenarios based on fuzz input
82
0
  cmFileLock lock;
83
84
  // Vary the lock file path based on remaining input
85
134
  std::string lockPath = lockFile;
86
134
  if (size > 1 && (flags & 0x01)) {
87
    // Use part of input as filename suffix (sanitized)
88
130
    size_t nameLen = std::min(size - 1, size_t(32));
89
130
    std::string suffix;
90
1.93k
    for (size_t i = 0; i < nameLen; ++i) {
91
1.80k
      char c = static_cast<char>(data[1 + i]);
92
      // Only allow safe filename characters
93
1.80k
      if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
94
1.30k
          (c >= '0' && c <= '9') || c == '_' || c == '-') {
95
1.01k
        suffix += c;
96
1.01k
      }
97
1.80k
    }
98
130
    if (!suffix.empty()) {
99
95
      lockPath = g_testDir + "/" + suffix + ".lock";
100
95
    }
101
130
  }
102
103
  // Test symlink scenario (security-critical)
104
134
  if (flags & 0x02) {
105
    // Create a symlink to the test file
106
69
    std::string symlinkPath = g_testDir + "/symlink.lock";
107
69
    unlink(symlinkPath.c_str());
108
69
    if (symlink(testFile.c_str(), symlinkPath.c_str()) == 0) {
109
69
      lockPath = symlinkPath;
110
69
    }
111
69
  }
112
113
  // Determine timeout - use 0 for fuzzing to avoid blocking
114
  // (non-zero timeouts would stall the fuzzer)
115
134
  unsigned long timeout = 0;
116
117
  // Try to acquire lock
118
134
  cmFileLockResult result = lock.Lock(lockPath, timeout);
119
134
  (void)result.IsOk();
120
121
  // Always try to release
122
134
  (void)lock.Release();
123
124
  // Security check: Verify test file wasn't truncated
125
134
  {
126
134
    FILE* fp = fopen(testFile.c_str(), "r");
127
134
    if (fp) {
128
134
      char buffer[256] = { 0 };
129
134
      size_t bytesRead = fread(buffer, 1, sizeof(buffer) - 1, fp);
130
134
      fclose(fp);
131
132
134
      if (bytesRead == 0 || strcmp(buffer, testContent) != 0) {
133
        // DATA DESTRUCTION DETECTED!
134
0
        fprintf(stderr, "VULNERABILITY: File was truncated or modified!\n");
135
0
        fprintf(stderr, "Expected: '%s'\n", testContent);
136
0
        fprintf(stderr, "Got: '%s' (%zu bytes)\n", buffer, bytesRead);
137
0
        abort();
138
0
      }
139
134
    }
140
134
  }
141
142
  // Cleanup
143
134
  unlink(lockPath.c_str());
144
134
  unlink((g_testDir + "/symlink.lock").c_str());
145
146
134
  return 0;
147
134
}