Coverage Report

Created: 2026-02-09 06:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Tests/Fuzzing/cmScriptFuzzer.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 script execution
6
 *
7
 * This fuzzer executes CMake scripts in script mode (-P).
8
 * This exercises the majority of CMake's codebase including:
9
 * - All built-in commands
10
 * - Variable expansion
11
 * - Control flow (if, foreach, while, function, macro)
12
 * - String/list/file operations
13
 * - Generator expressions
14
 *
15
 * This is the highest-impact fuzzer for coverage.
16
 *
17
 * Performance notes:
18
 * - Uses memfd_create on Linux for memory-backed file I/O
19
 * - Falls back to temp files on other platforms
20
 */
21
22
#include <cstddef>
23
#include <cstdint>
24
#include <cstdio>
25
#include <cstdlib>
26
#include <string>
27
#include <vector>
28
29
#include <unistd.h>
30
31
#include "cmCMakePolicyCommand.h"
32
#include "cmExecutionStatus.h"
33
#include "cmGlobalGenerator.h"
34
#include "cmMakefile.h"
35
#include "cmMessenger.h"
36
#include "cmState.h"
37
#include "cmStateSnapshot.h"
38
#include "cmSystemTools.h"
39
#include "cmake.h"
40
41
#ifdef __linux__
42
#  include <sys/mman.h>
43
#  ifndef MFD_CLOEXEC
44
#    define MFD_CLOEXEC 0x0001U
45
#  endif
46
#endif
47
48
static constexpr size_t kMaxInputSize = 256 * 1024;
49
static std::string g_testDir;
50
static std::string g_scriptFile;
51
static bool g_useMemfd = false;
52
53
extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv)
54
2
{
55
2
  (void)argc;
56
2
  (void)argv;
57
58
  // Suppress output during fuzzing (set once at init)
59
2
  cmSystemTools::SetMessageCallback(
60
3
    [](std::string const&, cmMessageMetadata const&) {});
61
2
  cmSystemTools::SetStdoutCallback([](std::string const&) {});
62
2
  cmSystemTools::SetStderrCallback([](std::string const&) {});
63
64
  // Create unique test directory (even with memfd, scripts can create files)
65
2
  char tmpl[] = "/tmp/cmake_fuzz_script_XXXXXX";
66
2
  char* dir = mkdtemp(tmpl);
67
2
  if (dir) {
68
2
    g_testDir = dir;
69
2
  } else {
70
0
    g_testDir = "/tmp/cmake_fuzz_script";
71
0
    cmSystemTools::MakeDirectory(g_testDir);
72
0
  }
73
74
2
#ifdef __linux__
75
  // Try to use memfd for better performance
76
2
  int fd = memfd_create("cmake_fuzz", MFD_CLOEXEC);
77
2
  if (fd >= 0) {
78
2
    g_useMemfd = true;
79
    // Create path via /proc/self/fd
80
2
    g_scriptFile = "/proc/self/fd/" + std::to_string(fd);
81
    // Keep fd open - will be reused
82
2
  } else
83
0
#endif
84
0
  {
85
0
    g_scriptFile = g_testDir + "/fuzz_script.cmake";
86
0
  }
87
88
2
  return 0;
89
2
}
90
91
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
92
35
{
93
35
  if (size == 0 || size > kMaxInputSize) {
94
0
    return 0;
95
0
  }
96
97
35
#ifdef __linux__
98
35
  if (g_useMemfd) {
99
    // Extract fd from path and write directly
100
35
    int fd = std::atoi(g_scriptFile.c_str() + 14); // "/proc/self/fd/"
101
35
    ftruncate(fd, 0);
102
35
    lseek(fd, 0, SEEK_SET);
103
35
    if (write(fd, data, size) != static_cast<ssize_t>(size)) {
104
0
      return 0;
105
0
    }
106
35
  } else
107
0
#endif
108
0
  {
109
    // Write script to temp file
110
0
    FILE* fp = fopen(g_scriptFile.c_str(), "wb");
111
0
    if (!fp)
112
0
      return 0;
113
0
    fwrite(data, 1, size, fp);
114
0
    fclose(fp);
115
0
  }
116
117
  // Save CWD in case script uses file(CHDIR)
118
35
  std::string cwd = cmSystemTools::GetCurrentWorkingDirectory();
119
120
  // Create cmake instance for script mode
121
35
  cmake cm(cmState::Role::Script);
122
35
  cm.SetHomeDirectory(g_testDir);
123
35
  cm.SetHomeOutputDirectory(g_testDir);
124
125
  // Run the script
126
35
  std::vector<std::string> args;
127
35
  args.push_back("cmake");
128
35
  args.push_back("-P");
129
35
  args.push_back(g_scriptFile);
130
131
35
  (void)cm.Run(args, false);
132
133
  // Restore CWD before cleanup (script may have changed it via file(CHDIR))
134
35
  cmSystemTools::ChangeDirectory(cwd);
135
136
  // Cleanup temp file (memfd doesn't need cleanup)
137
35
  if (!g_useMemfd) {
138
0
    unlink(g_scriptFile.c_str());
139
0
  }
140
141
  // Clean up any files the script may have created in g_testDir
142
  // This prevents disk growth and non-determinism from previous iterations
143
35
  cmSystemTools::RemoveADirectory(g_testDir);
144
35
  cmSystemTools::MakeDirectory(g_testDir);
145
146
35
  return 0;
147
35
}