From e8980ea33f0e28a3d6a298cf0546c3f2c8d2cb86 Mon Sep 17 00:00:00 2001 From: David Korczynski Date: Tue, 31 Mar 2026 03:24:39 -0700 Subject: [PATCH] add four new fuzzing harnesses The main goal here is to increase code coverage of the OSS-Fuzz project. A recent code coverage report is available here: https://storage.googleapis.com/oss-fuzz-coverage/pacemaker/reports/20260331/linux/src/report.html Signed-off-by: David Korczynski --- lib/common/fuzzers/acl_fuzzer.c | 55 ++++++++++++++++++ lib/common/fuzzers/patchset_fuzzer.c | 82 +++++++++++++++++++++++++++ lib/common/fuzzers/rules_fuzzer.c | 56 ++++++++++++++++++ lib/common/fuzzers/xml_parse_fuzzer.c | 55 ++++++++++++++++++ 4 files changed, 248 insertions(+) create mode 100644 lib/common/fuzzers/acl_fuzzer.c create mode 100644 lib/common/fuzzers/patchset_fuzzer.c create mode 100644 lib/common/fuzzers/rules_fuzzer.c create mode 100644 lib/common/fuzzers/xml_parse_fuzzer.c diff --git a/lib/common/fuzzers/acl_fuzzer.c b/lib/common/fuzzers/acl_fuzzer.c new file mode 100644 index 00000000000..a81264bd385 --- /dev/null +++ b/lib/common/fuzzers/acl_fuzzer.c @@ -0,0 +1,55 @@ +/* + * Copyright 2026 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#include + +#include +#include +#include + +#include +#include +#include + +int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + char *input = NULL; + xmlNode *xml = NULL; + xmlNode *result = NULL; + + if (size < 20) { + return -1; + } + + // Null-terminate the fuzz input + input = pcmk__assert_alloc(size + 1, sizeof(char)); + memcpy(input, data, size); + input[size] = '\0'; + + // Parse the fuzz input as XML + xml = pcmk__xml_parse(input); + if (xml == NULL) { + free(input); + return 0; + } + + // Run the ACL filtered copy with a non-root user + // pcmk_acl_required() returns false for "root" and "hacluster", so we use + // a regular user name to ensure ACL processing is actually exercised. + xml_acl_filtered_copy("fuzzuser", xml, xml, &result); + + if (result != NULL) { + pcmk__xml_free(result); + } + + pcmk__xml_free(xml); + free(input); + return 0; +} diff --git a/lib/common/fuzzers/patchset_fuzzer.c b/lib/common/fuzzers/patchset_fuzzer.c new file mode 100644 index 00000000000..0998b071e72 --- /dev/null +++ b/lib/common/fuzzers/patchset_fuzzer.c @@ -0,0 +1,82 @@ +/* + * Copyright 2026 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#include + +#include +#include +#include + +#include +#include +#include + +/* A minimal but realistic CIB structure that the patchset will be applied to. + * This provides enough structure for XPath operations to have meaningful + * targets (nodes, resources, constraints, status). + */ +static const char *BASE_CIB = + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; + +int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + char *input = NULL; + xmlNode *patchset = NULL; + xmlNode *cib = NULL; + + if (size < 15) { + return -1; + } + + // Parse a fresh copy of the base CIB for each iteration + cib = pcmk__xml_parse(BASE_CIB); + if (cib == NULL) { + return 0; + } + + // Parse the fuzz input as a patchset + input = pcmk__assert_alloc(size + 1, sizeof(char)); + memcpy(input, data, size); + input[size] = '\0'; + + patchset = pcmk__xml_parse(input); + if (patchset == NULL) { + pcmk__xml_free(cib); + free(input); + return 0; + } + + // Apply the fuzz-generated patchset to the base CIB + // Disable version checking to maximize code path exploration + xml_apply_patchset(cib, patchset, false); + + pcmk__xml_free(patchset); + pcmk__xml_free(cib); + free(input); + return 0; +} diff --git a/lib/common/fuzzers/rules_fuzzer.c b/lib/common/fuzzers/rules_fuzzer.c new file mode 100644 index 00000000000..e908bff138a --- /dev/null +++ b/lib/common/fuzzers/rules_fuzzer.c @@ -0,0 +1,56 @@ +/* + * Copyright 2026 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#include + +#include +#include +#include +#include + +#include +#include +#include + +int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + char *input = NULL; + xmlNode *xml = NULL; + crm_time_t *now = NULL; + pcmk_rule_input_t rule_input = { NULL, }; + + if (size < 10) { + return -1; + } + + // Null-terminate the fuzz input + input = pcmk__assert_alloc(size + 1, sizeof(char)); + memcpy(input, data, size); + input[size] = '\0'; + + // Parse the fuzz input as XML — rules are always XML-based + xml = pcmk__xml_parse(input); + if (xml == NULL) { + free(input); + return 0; + } + + // Set up a minimal rule evaluation context with a fixed "now" time + now = pcmk__copy_timet(1700000000); // Fixed time for determinism + rule_input.now = now; + + // Evaluate the parsed XML as a rule + pcmk_evaluate_rule(xml, &rule_input, NULL); + + crm_time_free(now); + pcmk__xml_free(xml); + free(input); + return 0; +} diff --git a/lib/common/fuzzers/xml_parse_fuzzer.c b/lib/common/fuzzers/xml_parse_fuzzer.c new file mode 100644 index 00000000000..a385eee5c3b --- /dev/null +++ b/lib/common/fuzzers/xml_parse_fuzzer.c @@ -0,0 +1,55 @@ +/* + * Copyright 2026 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#include + +#include +#include +#include + +#include +#include + +int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + char *input = NULL; + xmlNode *xml = NULL; + + if (size < 5) { + return -1; + } + + // Null-terminate the input to create a valid C string + input = pcmk__assert_alloc(size + 1, sizeof(char)); + memcpy(input, data, size); + input[size] = '\0'; + + // Parse the XML string — this is the core function under test + xml = pcmk__xml_parse(input); + + // If parsing succeeded, exercise some read-only operations on the result + if (xml != NULL) { + // Access the element name and ID (common post-parse operations) + pcmk__xe_id(xml); + + // Iterate children — exercises XML tree traversal + for (xmlNode *child = pcmk__xe_first_child(xml, NULL, NULL, NULL); + child != NULL; + child = pcmk__xe_next(child, NULL)) { + + pcmk__xe_id(child); + } + + pcmk__xml_free(xml); + } + + free(input); + return 0; +}