/src/CMake/Tests/Fuzzing/cmPackageInfoReaderFuzzer.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 CPS (Common Package Specification) reader. |
6 | | * |
7 | | * cmPackageInfoReader.cxx is 694 lines at 0.00% coverage. It parses the .cps |
8 | | * JSON files that third-party packages install and that find_package() picks |
9 | | * up from whatever is on the search path, so the input is supplied by the |
10 | | * packages found on the machine rather than by the project being built. |
11 | | * |
12 | | * cmJSONParserFuzzer already covers jsoncpp itself. What is untested is |
13 | | * everything the reader does with the decoded value: schema-version gating, |
14 | | * prefix resolution against the file's own location, the "simple"/pep440 |
15 | | * version grammar, requirement specs, and -- the bulk of the file -- turning |
16 | | * each entry under "components" into an imported target, with per- |
17 | | * configuration properties, link/compile features, definitions, file sets and |
18 | | * C++ module metadata. |
19 | | * |
20 | | * Reaching the target-import half needs a real cmMakefile, so each input gets |
21 | | * a fresh cmake/cmGlobalGenerator/cmMakefile fixture. That also keeps targets |
22 | | * created by one input from colliding with the next. |
23 | | * |
24 | | * The reader resolves its prefix by matching "cps_path" against the directory |
25 | | * the file was read from, so the input has to be a real file in a real |
26 | | * directory: the fixture writes it to <tmp>/cps/fuzz.cps, mirroring the layout |
27 | | * the upstream CPS tests use ("cps_path": "@prefix@/cps"). |
28 | | * |
29 | | * Both the primary and the appendix read run on every input. An appendix is a |
30 | | * supplemental file read with an already-parsed package as its parent, which |
31 | | * skips the schema-version check and inherits the parent's prefix, components |
32 | | * and default configurations -- a different path through Read() that no |
33 | | * primary read can reach. |
34 | | */ |
35 | | |
36 | | #include <cstddef> |
37 | | #include <cstdint> |
38 | | #include <cstdio> |
39 | | #include <memory> |
40 | | #include <string> |
41 | | |
42 | | #include <cm/memory> |
43 | | #include <cm/optional> |
44 | | |
45 | | #include "cmExecutionStatus.h" |
46 | | #include "cmGlobalGenerator.h" |
47 | | #include "cmMakefile.h" |
48 | | #include "cmMessenger.h" |
49 | | #include "cmPackageInfoReader.h" |
50 | | #include "cmState.h" |
51 | | #include "cmStateDirectory.h" |
52 | | #include "cmStateSnapshot.h" |
53 | | #include "cmSystemTools.h" |
54 | | #include "cmTargetTypes.h" |
55 | | #include "cmake.h" |
56 | | |
57 | | static constexpr size_t kMaxInputSize = 256 * 1024; |
58 | | |
59 | | static std::string g_prefixDir; |
60 | | static std::string g_cpsFile; |
61 | | |
62 | | extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) |
63 | 12 | { |
64 | 12 | (void)argc; |
65 | 12 | (void)argv; |
66 | | |
67 | 12 | cmSystemTools::SetMessageCallback( |
68 | 24.1k | [](std::string const&, cmMessageMetadata const&) {}); |
69 | 12 | cmSystemTools::SetStdoutCallback([](std::string const&) {}); |
70 | 12 | cmSystemTools::SetStderrCallback([](std::string const&) {}); |
71 | | |
72 | 12 | char tmpl[] = "/tmp/cmake_fuzz_cps_XXXXXX"; |
73 | 12 | char* dir = mkdtemp(tmpl); |
74 | 12 | if (dir) { |
75 | 12 | g_prefixDir = dir; |
76 | 12 | } else { |
77 | 0 | g_prefixDir = "/tmp/cmake_fuzz_cps"; |
78 | 0 | } |
79 | | |
80 | 12 | cmSystemTools::MakeDirectory(g_prefixDir + "/cps"); |
81 | 12 | g_cpsFile = g_prefixDir + "/cps/fuzz.cps"; |
82 | | |
83 | 12 | return 0; |
84 | 12 | } |
85 | | |
86 | | namespace { |
87 | | |
88 | | struct Fixture |
89 | | { |
90 | | cmake CMake{ cmState::Role::Project }; |
91 | | std::unique_ptr<cmGlobalGenerator> GG; |
92 | | std::unique_ptr<cmMakefile> MF; |
93 | | |
94 | | Fixture() |
95 | 13.9k | { |
96 | 13.9k | this->CMake.SetHomeDirectory(g_prefixDir); |
97 | 13.9k | this->CMake.SetHomeOutputDirectory(g_prefixDir); |
98 | 13.9k | this->GG = cm::make_unique<cmGlobalGenerator>(&this->CMake); |
99 | 13.9k | cmStateSnapshot snapshot = this->CMake.GetCurrentSnapshot(); |
100 | 13.9k | snapshot.GetDirectory().SetCurrentBinary(g_prefixDir); |
101 | 13.9k | snapshot.GetDirectory().SetCurrentSource(g_prefixDir); |
102 | 13.9k | this->MF = cm::make_unique<cmMakefile>(this->GG.get(), snapshot); |
103 | 13.9k | } |
104 | | }; |
105 | | |
106 | | void Consume(cmPackageInfoReader const& reader) |
107 | 8.55k | { |
108 | 8.55k | (void)reader.GetName(); |
109 | | |
110 | 8.55k | cm::optional<std::string> const version = reader.GetVersion(); |
111 | 8.55k | cm::optional<std::string> const compatVersion = reader.GetCompatVersion(); |
112 | 8.55k | (void)reader.ParseVersion(version); |
113 | 8.55k | (void)reader.ParseVersion(compatVersion); |
114 | | |
115 | 8.55k | for (cmPackageRequirement const& req : reader.GetRequirements()) { |
116 | 3.17k | (void)req.Name; |
117 | 3.17k | (void)req.Version; |
118 | 3.17k | (void)req.Components; |
119 | 3.17k | (void)req.Hints; |
120 | 3.17k | } |
121 | 8.55k | (void)reader.GetComponentNames(); |
122 | 8.55k | } |
123 | | |
124 | | } // namespace |
125 | | |
126 | | extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) |
127 | 6.99k | { |
128 | 6.99k | if (size == 0 || size > kMaxInputSize) { |
129 | 5 | return 0; |
130 | 5 | } |
131 | | |
132 | 6.98k | { |
133 | 6.98k | FILE* fp = fopen(g_cpsFile.c_str(), "wb"); |
134 | 6.98k | if (!fp) { |
135 | 0 | return 0; |
136 | 0 | } |
137 | 6.98k | bool const written = fwrite(data, 1, size, fp) == size; |
138 | 6.98k | fclose(fp); |
139 | 6.98k | if (!written) { |
140 | 0 | return 0; |
141 | 0 | } |
142 | 6.98k | } |
143 | | |
144 | 6.98k | { |
145 | 6.98k | Fixture fx; |
146 | 6.98k | std::unique_ptr<cmPackageInfoReader> reader = |
147 | 6.98k | cmPackageInfoReader::Read(fx.MF.get(), g_cpsFile); |
148 | 6.98k | if (reader) { |
149 | 4.27k | Consume(*reader); |
150 | | |
151 | 4.27k | cmExecutionStatus status(*fx.MF); |
152 | 4.27k | if (reader->ImportTargets(fx.MF.get(), status, |
153 | 4.27k | cm::ImportedTargetScope::Local)) { |
154 | 4.27k | reader->ImportTargetConfigurations(fx.MF.get(), status); |
155 | 4.27k | } |
156 | 4.27k | } |
157 | 6.98k | } |
158 | | |
159 | 6.98k | { |
160 | 6.98k | Fixture fx; |
161 | 6.98k | std::unique_ptr<cmPackageInfoReader> parent = |
162 | 6.98k | cmPackageInfoReader::Read(fx.MF.get(), g_cpsFile); |
163 | 6.98k | if (parent) { |
164 | 4.27k | std::unique_ptr<cmPackageInfoReader> appendix = |
165 | 4.27k | cmPackageInfoReader::Read(fx.MF.get(), g_cpsFile, parent.get()); |
166 | 4.27k | if (appendix) { |
167 | 4.27k | Consume(*appendix); |
168 | | |
169 | 4.27k | cmExecutionStatus status(*fx.MF); |
170 | 4.27k | if (appendix->ImportTargets(fx.MF.get(), status, |
171 | 4.27k | cm::ImportedTargetScope::Global)) { |
172 | 4.27k | appendix->ImportTargetConfigurations(fx.MF.get(), status); |
173 | 4.27k | } |
174 | 4.27k | } |
175 | 4.27k | } |
176 | 6.98k | } |
177 | | |
178 | 6.98k | return 0; |
179 | 6.98k | } |