OSS-Fuzz: Add new fuzzer targets Package Info Reader

Signed-off-by: Arthur Chan <arthur.chan@adalogics.com>
This commit is contained in:
Arthur Chan authored and Brad King committed 2026-09-28 11:58:31 -04:00
1 parent ec85176fbe
commit 97c527aeb8
15 files changed
+476

No files matched your search

+6
View File
@@ -31,6 +31,12 @@
#include "cmTargetTypes.h"
#include "cmValue.h"
// When adding or changing CPS schema versions, component types, languages, or
// branch-selecting attributes, review Tests/Fuzzing/cmPackageInfoReader.dict.
// Add tokens including keys that help mutations reach the new paths; keep the
// dictionary focused on useful parser inputs, not an exhaustive list of CPS
// keywords.
namespace {
// Map of CPS language names to CMake language name. Case insensitivity is
+3
View File
@@ -104,3 +104,6 @@ add_fuzzer(cmScriptFuzzer cmScriptFuzzer.cxx)
# Fortran dependency scanner fuzzer
add_fuzzer(cmFortranParserFuzzer cmFortranParserFuzzer.cxx)
# CPS (Common Package Specification) package-info reader fuzzer
add_fuzzer(cmPackageInfoReaderFuzzer cmPackageInfoReaderFuzzer.cxx)
+63
View File
@@ -0,0 +1,63 @@
# Dictionary for the CPS (Common Package Specification) package-info reader.
# Keys and values that gate the reader's branches: the accepted schema range,
# the prefix marker, component types, and the per-configuration attributes.
# Top-level keys
"\"cps_version\""
"\"name\""
"\"version\""
"\"compat_version\""
"\"version_schema\""
"\"cps_path\""
"\"prefix\""
"\"components\""
"\"requires\""
"\"configurations\""
"\"default_configurations\""
"\"default_license\""
"\"license\""
# Accepted schema versions (reader requires >= 0.13 and < 0.16)
"\"0.13\""
"\"0.14\""
"\"0.15\""
# Prefix resolution
"\"@prefix@\""
"\"@prefix@/cps\""
# Version schemas
"\"simple\""
"\"custom\""
"\"rpm\""
"\"dpkg\""
"\"pep440\""
# Component types
"\"type\""
"\"interface\""
"\"executable\""
"\"archive\""
"\"dylib\""
"\"module\""
"\"symbolic\""
# Component attributes
"\"location\""
"\"includes\""
"\"definitions\""
"\"compile_flags\""
"\"link_flags\""
"\"link_libraries\""
"\"compile_features\""
"\"link_features\""
"\"requires\""
"\"configurations\""
"\"file_sets\""
"\"cxx_modules\""
# Languages accepted for per-language properties
"\"c\""
"\"cpp\""
"\"cxx\""
"\"fortran\""
+179
View File
@@ -0,0 +1,179 @@
/* 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 CPS (Common Package Specification) reader.
*
* cmPackageInfoReader.cxx is 694 lines at 0.00% coverage. It parses the .cps
* JSON files that third-party packages install and that find_package() picks
* up from whatever is on the search path, so the input is supplied by the
* packages found on the machine rather than by the project being built.
*
* cmJSONParserFuzzer already covers jsoncpp itself. What is untested is
* everything the reader does with the decoded value: schema-version gating,
* prefix resolution against the file's own location, the "simple"/pep440
* version grammar, requirement specs, and -- the bulk of the file -- turning
* each entry under "components" into an imported target, with per-
* configuration properties, link/compile features, definitions, file sets and
* C++ module metadata.
*
* Reaching the target-import half needs a real cmMakefile, so each input gets
* a fresh cmake/cmGlobalGenerator/cmMakefile fixture. That also keeps targets
* created by one input from colliding with the next.
*
* The reader resolves its prefix by matching "cps_path" against the directory
* the file was read from, so the input has to be a real file in a real
* directory: the fixture writes it to <tmp>/cps/fuzz.cps, mirroring the layout
* the upstream CPS tests use ("cps_path": "@prefix@/cps").
*
* Both the primary and the appendix read run on every input. An appendix is a
* supplemental file read with an already-parsed package as its parent, which
* skips the schema-version check and inherits the parent's prefix, components
* and default configurations -- a different path through Read() that no
* primary read can reach.
*/
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <memory>
#include <string>
#include <cm/memory>
#include <cm/optional>
#include "cmExecutionStatus.h"
#include "cmGlobalGenerator.h"
#include "cmMakefile.h"
#include "cmMessenger.h"
#include "cmPackageInfoReader.h"
#include "cmState.h"
#include "cmStateDirectory.h"
#include "cmStateSnapshot.h"
#include "cmSystemTools.h"
#include "cmTargetTypes.h"
#include "cmake.h"
static constexpr size_t kMaxInputSize = 256 * 1024;
static std::string g_prefixDir;
static std::string g_cpsFile;
extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv)
{
(void)argc;
(void)argv;
cmSystemTools::SetMessageCallback(
[](std::string const&, cmMessageMetadata const&) {});
cmSystemTools::SetStdoutCallback([](std::string const&) {});
cmSystemTools::SetStderrCallback([](std::string const&) {});
char tmpl[] = "/tmp/cmake_fuzz_cps_XXXXXX";
char* dir = mkdtemp(tmpl);
if (dir) {
g_prefixDir = dir;
} else {
g_prefixDir = "/tmp/cmake_fuzz_cps";
}
cmSystemTools::MakeDirectory(g_prefixDir + "/cps");
g_cpsFile = g_prefixDir + "/cps/fuzz.cps";
return 0;
}
namespace {
struct Fixture
{
cmake CMake{ cmState::Role::Project };
std::unique_ptr<cmGlobalGenerator> GG;
std::unique_ptr<cmMakefile> MF;
Fixture()
{
this->CMake.SetHomeDirectory(g_prefixDir);
this->CMake.SetHomeOutputDirectory(g_prefixDir);
this->GG = cm::make_unique<cmGlobalGenerator>(&this->CMake);
cmStateSnapshot snapshot = this->CMake.GetCurrentSnapshot();
snapshot.GetDirectory().SetCurrentBinary(g_prefixDir);
snapshot.GetDirectory().SetCurrentSource(g_prefixDir);
this->MF = cm::make_unique<cmMakefile>(this->GG.get(), snapshot);
}
};
void Consume(cmPackageInfoReader const& reader)
{
(void)reader.GetName();
cm::optional<std::string> const version = reader.GetVersion();
cm::optional<std::string> const compatVersion = reader.GetCompatVersion();
(void)reader.ParseVersion(version);
(void)reader.ParseVersion(compatVersion);
for (cmPackageRequirement const& req : reader.GetRequirements()) {
(void)req.Name;
(void)req.Version;
(void)req.Components;
(void)req.Hints;
}
(void)reader.GetComponentNames();
}
} // namespace
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
{
if (size == 0 || size > kMaxInputSize) {
return 0;
}
{
FILE* fp = fopen(g_cpsFile.c_str(), "wb");
if (!fp) {
return 0;
}
bool const written = fwrite(data, 1, size, fp) == size;
fclose(fp);
if (!written) {
return 0;
}
}
{
Fixture fx;
std::unique_ptr<cmPackageInfoReader> reader =
cmPackageInfoReader::Read(fx.MF.get(), g_cpsFile);
if (reader) {
Consume(*reader);
cmExecutionStatus status(*fx.MF);
if (reader->ImportTargets(fx.MF.get(), status,
cm::ImportedTargetScope::Local)) {
reader->ImportTargetConfigurations(fx.MF.get(), status);
}
}
}
{
Fixture fx;
std::unique_ptr<cmPackageInfoReader> parent =
cmPackageInfoReader::Read(fx.MF.get(), g_cpsFile);
if (parent) {
std::unique_ptr<cmPackageInfoReader> appendix =
cmPackageInfoReader::Read(fx.MF.get(), g_cpsFile, parent.get());
if (appendix) {
Consume(*appendix);
cmExecutionStatus status(*fx.MF);
if (appendix->ImportTargets(fx.MF.get(), status,
cm::ImportedTargetScope::Global)) {
appendix->ImportTargetConfigurations(fx.MF.get(), status);
}
}
}
}
return 0;
}
@@ -0,0 +1,24 @@
{
"cps_version": "0.13",
"name": "DefaultConfigurationsTest",
"cps_path": "@prefix@/cps",
"configurations": [ "Default" ],
"components": {
"Target1": {
"type": "interface",
"configurations": {
"Test": {
"includes": [ "@prefix@/include" ]
}
}
},
"Target2": {
"type": "interface",
"configurations": {
"Test": {
"includes": [ "@prefix@/include" ]
}
}
}
}
}
+49
View File
@@ -0,0 +1,49 @@
{
"cps_version": "0.14.0",
"name": "RequiresTest",
"cps_path": "@prefix@/cps",
"components": {
"Indirect": {
"type": "interface",
"requires": [ ":Direct" ]
},
"Direct": {
"type": "interface",
"definitions": {
"*": {
"ANSWER": 42
}
}
},
"CompileOnly": {
"type": "interface",
"compile_requires": [ ":BrokenLibrary" ]
},
"BrokenLibrary": {
"type": "archive",
"location": "@prefix@/lib/does-not-exist.a",
"definitions": {
"*": {
"ANSWER": 42
}
}
},
"Private1": {
"type": "dylib"
},
"Private2": {
"type": "dylib"
},
"Other": {
"type": "dylib",
"link_libraries": [ "every-config" ],
"dyld_requires": [ ":Private1" ],
"configurations": {
"test": {
"link_libraries": [ "test-config" ],
"dyld_requires": [ ":Private2" ]
}
}
}
}
}
@@ -0,0 +1,11 @@
{
"cps_version": "0.13",
"name": "TransitiveTest",
"cps_path": "@prefix@/cps",
"requires": {
"TransitiveDep": {
"components": [ "Target2" ]
}
},
"components": {}
}
+11
View File
@@ -0,0 +1,11 @@
{
"cps_version": "0.13",
"name": "Bad1",
"cps_path": "@prefix@/cps",
"requires": "Broken",
"components": {
"Target": {
"type": "interface"
}
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"cps_version": "0.13",
"name": "BadVersion2",
"version": "1.1a.0",
"version_schema": "simple",
"cps_path": "@prefix@/cps",
"components": {}
}
@@ -0,0 +1,8 @@
{
"cps_version": "0.13",
"name": "CustomVersion",
"version": "VII",
"version_schema": "roman",
"cps_path": "@prefix@/cps",
"components": {}
}
+27
View File
@@ -0,0 +1,27 @@
{
"cps_version": "0.13",
"name": "defs",
"cps_path": "@prefix@/cps",
"components": {
"defs": {
"type": "interface",
"definitions": {
"c": {
"ONLY_IN_C": null,
"OVERRIDE1": "1",
"OVERRIDE2": "1"
},
"cxx": {
"ONLY_IN_CXX": null,
"OVERRIDE1": "2"
},
"*": {
"NOVALUE": null,
"EMPTYVALUE": "",
"OVERRIDE1": "0",
"OVERRIDE2": "0"
}
}
}
}
}
@@ -0,0 +1,49 @@
{
"cps_version": "0.15.0",
"name": "FileSets-Named",
"cps_path": "@prefix@/cps",
"components": {
"Target": {
"file_sets":
[
{
"extensions":
{
"cmake":
{
"name@v1": "foo"
}
},
"type": "includes",
"root": "@prefix@/include/a",
"files": [ "foo1.h", "foo2.h" ]
},
{
"extensions":
{
"cmake":
{
"name@v1": "foo"
}
},
"type": "includes",
"root": "@prefix@/include/b",
"files": [ "foo3.h" ]
},
{
"extensions":
{
"cmake":
{
"name@v1": "bar"
}
},
"type": "includes",
"root": "@prefix@/include",
"files": [ "bar.h" ]
}
],
"type": "interface"
}
}
}
+17
View File
@@ -0,0 +1,17 @@
{
"cps_version": "0.13",
"name": "Foo",
"cps_path": "@prefix@/cps",
"default_configurations": ["default"],
"components": {
"PrefixTest": {
"type": "interface"
},
"Empty": {
"type": "interface"
},
"ExecutableTest": {
"type": "executable"
}
}
}
+13
View File
@@ -0,0 +1,13 @@
{
"cps_version": "0.13",
"name": "Foo",
"configuration": "default",
"components": {
"PrefixTest": {
"includes": ["@prefix@/include"]
},
"ExecutableTest": {
"location": "@prefix@/foo"
}
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"cps_version": "0.13",
"name": "Sample",
"version": "2.10.11",
"compat_version": "2.0.0",
"cps_path": "@prefix@/cps",
"components": {}
}