Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions lib/common/fuzzers/acl_fuzzer.c
Original file line number Diff line number Diff line change
@@ -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 <crm_internal.h>

#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#include <crm/common/util.h>
#include <crm/common/internal.h>
#include <crm/common/acl.h>

int
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
char *input = NULL;
xmlNode *xml = NULL;
xmlNode *result = NULL;

if (size < 20) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get comments explaining the magic size numbers, here and in the other harnesses?

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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want, you can do all the freeing after a done: label and just goto done here. We frequently do cleanup using goto done, even though we don't use goto for much of anything else. It helps us not to forget to free anything in early returns.

It doesn't matter to me. Note that this applies to other files in this commit too.

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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be worth fuzzing with different usernames (returning -1 for "root" or "hacluster" to exclude those from the corpus)? I'm new to libfuzzer and unsure of best practices for coverage.

Speaking of which... I don't know whether or how the corpus gets seeded with valid inputs. If we're relying on purely random data here, I would expect it to take an unreasonably long time to get anything remotely meaningful, even if we get valid XML. If all we care about is testing garbage input, that's fine.


if (result != NULL) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't need to be guarded. pcmk__xml_free() is NULL-safe.

pcmk__xml_free(result);
}

pcmk__xml_free(xml);
free(input);
return 0;
}
82 changes: 82 additions & 0 deletions lib/common/fuzzers/patchset_fuzzer.c
Original file line number Diff line number Diff line change
@@ -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 <crm_internal.h>

#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#include <crm/common/util.h>
#include <crm/common/internal.h>
#include <crm/common/xml.h>

/* 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 =
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We usually reserve all-caps for #define'd constants. I'd prefer to see this as either a #define or base_cib

"<cib admin_epoch=\"1\" epoch=\"1\" num_updates=\"0\">"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure you can use single quotes within the XML and avoid all the backslash-escaping

" <configuration>"
" <crm_config>"
" <cluster_property_set id=\"cib-bootstrap-options\">"
" <nvpair id=\"opt1\" name=\"stonith-enabled\" value=\"false\"/>"
" </cluster_property_set>"
" </crm_config>"
" <nodes>"
" <node id=\"node1\" uname=\"pcmk-1\"/>"
" <node id=\"node2\" uname=\"pcmk-2\"/>"
" </nodes>"
" <resources>"
" <primitive id=\"rsc1\" class=\"ocf\" provider=\"heartbeat\""
" type=\"Dummy\"/>"
" </resources>"
" <constraints/>"
" </configuration>"
" <status/>"
"</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;
}
56 changes: 56 additions & 0 deletions lib/common/fuzzers/rules_fuzzer.c
Original file line number Diff line number Diff line change
@@ -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 <crm_internal.h>

#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <crm/common/util.h>
#include <crm/common/internal.h>
#include <crm/common/rules.h>

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;
}
55 changes: 55 additions & 0 deletions lib/common/fuzzers/xml_parse_fuzzer.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should either change the name of this file or change our documentation.

That directory has a file for each fuzzed source file, named the same except ending in _fuzzer.c (for example, lib/common/fuzzers/strings_fuzzer.c has fuzzing for lib/common/strings.c).

There is no xml_parse.c file.

* 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 <crm_internal.h>

#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#include <crm/common/util.h>
#include <crm/common/internal.h>

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) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might as well be consistent with the other three files in this commit, and return early if xml == NULL, de-nesting the body of this if statement.

// Access the element name and ID (common post-parse operations)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are we accessing the element name?

pcmk__xe_id(xml);

// Iterate children — exercises XML tree traversal
for (xmlNode *child = pcmk__xe_first_child(xml, NULL, NULL, NULL);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use const xmlNode *child

child != NULL;
child = pcmk__xe_next(child, NULL)) {

pcmk__xe_id(child);
}

pcmk__xml_free(xml);
}

free(input);
return 0;
}