From 8932f356bdd7ad98d3cc31229f23a8193ce159f6 Mon Sep 17 00:00:00 2001 From: Arthur Chan Date: Mon, 21 Sep 2026 18:03:06 +0100 Subject: [PATCH] OSS-Fuzz: Add new fuzzer targets pkg Config Resolver Signed-off-by: Arthur Chan --- Tests/Fuzzing/CMakeLists.txt | 1 + Tests/Fuzzing/cmPkgConfigResolver.dict | 63 ++++++++++++ Tests/Fuzzing/cmPkgConfigResolverFuzzer.cxx | 100 ++++++++++++++++++++ 3 files changed, 164 insertions(+) create mode 100644 Tests/Fuzzing/cmPkgConfigResolver.dict create mode 100644 Tests/Fuzzing/cmPkgConfigResolverFuzzer.cxx diff --git a/Tests/Fuzzing/CMakeLists.txt b/Tests/Fuzzing/CMakeLists.txt index a78bf253ac..d62d1b9059 100644 --- a/Tests/Fuzzing/CMakeLists.txt +++ b/Tests/Fuzzing/CMakeLists.txt @@ -67,6 +67,7 @@ add_fuzzer(cmExprParserFuzzer cmExprParserFuzzer.cxx) # pkg-config parser fuzzer add_fuzzer(cmPkgConfigParserFuzzer cmPkgConfigParserFuzzer.cxx) +add_fuzzer(cmPkgConfigResolverFuzzer cmPkgConfigResolverFuzzer.cxx) # JSON parser fuzzer add_fuzzer(cmJSONParserFuzzer cmJSONParserFuzzer.cxx) diff --git a/Tests/Fuzzing/cmPkgConfigResolver.dict b/Tests/Fuzzing/cmPkgConfigResolver.dict new file mode 100644 index 0000000000..f12af7596a --- /dev/null +++ b/Tests/Fuzzing/cmPkgConfigResolver.dict @@ -0,0 +1,63 @@ +# pkg-config File Dictionary + +# Key-value separators +"=" +":" + +# Variable reference +"${" +"}" + +# Common keywords +"Name" +"Description" +"Version" +"Requires" +"Requires.private" +"Conflicts" +"Libs" +"Libs.private" +"Cflags" +"URL" + +# Common variables +"prefix" +"exec_prefix" +"libdir" +"includedir" +"datarootdir" +"datadir" +"sysconfdir" +"sharedstatedir" +"localstatedir" + +# Common values +"/usr" +"/usr/local" +"/usr/lib" +"/usr/include" +"-I" +"-L" +"-l" +"-D" +"-pthread" + +# Whitespace +" " +"\x09" +"\x0a" +"\x0d\x0a" + +# Comments +"#" + +# Version operators +">=" +"<=" +">" +"<" +"!=" + +# Escapes +"\\" +"\\$" diff --git a/Tests/Fuzzing/cmPkgConfigResolverFuzzer.cxx b/Tests/Fuzzing/cmPkgConfigResolverFuzzer.cxx new file mode 100644 index 0000000000..14acdca3c4 --- /dev/null +++ b/Tests/Fuzzing/cmPkgConfigResolverFuzzer.cxx @@ -0,0 +1,100 @@ +/* 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 pkg-config resolver. + * + * cmPkgConfigParserFuzzer already covers the .pc tokeniser, but it stops at + * the raw key/value pairs. Everything downstream of that is untested because + * the resolver is what drives its variable-interpolation entry points. + * + * The resolver is where the interesting work happens: recursive ${var} + * expansion, -I/-L/-l fragment splitting, sysroot re-rooting, and parsing + * "Requires:" dependency specs with version operators. .pc files come from + * whatever is on PKG_CONFIG_PATH, so a hostile or corrupt package drop is a + * realistic source of malformed input. + * + * All three strictness levels run on every input. Strictness is not chosen by + * the input -- Strict and Permissive reject files that BestEffort accepts, so + * running all three is what reaches the accept/reject paths in + * HasRequiredFields() and HasStrictConflicts(). + */ + +#include +#include +#include +#include + +#include + +#include "cmPkgConfigParser.h" +#include "cmPkgConfigResolver.h" + +static constexpr size_t kMaxInputSize = 64 * 1024; + +static cmPkgConfigParser ParseInput(uint8_t const* data, size_t size) +{ + // cmPkgConfigParser::Parse takes a non-const buffer (may modify in place) + std::vector buffer(data, data + size); + + cmPkgConfigParser parser; + parser.Parse(buffer.data(), buffer.size()); + parser.Finish(); + parser.ParseComplete(); + + return parser; +} + +static void Consume(cmPkgConfigResolver const& resolver) +{ + (void)resolver.Name(); + (void)resolver.Description(); + (void)resolver.Version(); + + // Dependency specs: version operators and the spec grammar. + for (auto const& spec : resolver.Requires(false)) { + (void)cmPkgConfigResolver::VersionReqString(spec); + (void)cmPkgConfigResolver::CheckVersion(spec, "1.2.3"); + } + (void)resolver.Requires(true); + (void)resolver.Conflicts(); + (void)resolver.Provides(); + + // Fragment splitting and sysroot re-rooting, public and private. + (void)resolver.Cflags(false); + (void)resolver.Cflags(true); + (void)resolver.Libs(false); + (void)resolver.Libs(true); +} + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + if (size == 0 || size > kMaxInputSize) { + return 0; + } + + cmPkgConfigEnv env; + env.SysrootDir = std::string("/fuzz/sysroot"); + env.TopBuildDir = std::string("/fuzz/build"); + env.LibDirs = std::vector{ "/usr/lib", "/fuzz/lib" }; + env.SysCflags = std::vector{ "/usr/include" }; + env.SysLibs = std::vector{ "/usr/lib" }; + + std::string const pcFileDir = "/fuzz/pkgconfig"; + + cmPkgConfigParser const parsed = ParseInput(data, size); + + if (auto strict = + cmPkgConfigResolver::ResolveStrict(parsed, env, pcFileDir)) { + Consume(*strict); + } + + if (auto permissive = + cmPkgConfigResolver::ResolvePermissive(parsed, env, pcFileDir)) { + Consume(*permissive); + } + + Consume(cmPkgConfigResolver::ResolveBestEffort(parsed, env, pcFileDir)); + + return 0; +}