Skip to content

XCCDF/OVAL/CPE/DS: fix NULL-pointer dereferences on malformed input#2361

Open
edznux-dd wants to merge 2 commits into
OpenSCAP:mainfrom
edznux-dd:fix/null-pointer-deref
Open

XCCDF/OVAL/CPE/DS: fix NULL-pointer dereferences on malformed input#2361
edznux-dd wants to merge 2 commits into
OpenSCAP:mainfrom
edznux-dd:fix/null-pointer-deref

Conversation

@edznux-dd
Copy link
Copy Markdown

@edznux-dd edznux-dd commented Jun 3, 2026

Hey :)

I was looking at some of our logs and noticed that openscap was crashing on memory corruption issues, so I decided to dig a bit into it and write some fuzz test (I didn't have the exact source of the crashes).

This is the 1st of 4 fix PR (+ one for the fuzz test), that I tried to split into manageable diff.

(There will be one PR that contains the fuzz-harnesses that I used. They are not comprehensive for all the code path

This current PR mostly ensure that all references are guarded by null checks.

Notes:

  • I've read your contribution guideline, and I believe I'm targeting the correct branch, but I might be mistaken?
  • I've not created associated individual issues as I believe it creates unnecessary toil when I had to fix the bugs to continue fuzzing anyway (a lot of these bugs where found iteratively, run fuzz -> bug fix -> run fuzz -> bug fix ...)
  • I've let all the fuzz test run for 24h once all bug were fixed (not on a huge amount of cores, sure, but still, seems fairly robust now, under the current harnesses.)

For some context on this specific PR:

  • the missing XML attributes/namespaces/text make libxml return NULL, which was then passed to strcmp/atoi/atof or dereferenced.
  • common/util.h: oscap_str_startswith()/oscap_str_endswith() return false on a NULL string (matching oscap_strcmp/oscap_streq).
  • OVAL: guard the optional version attribute before atoi() in oval_state, oval_definition, oval_object, oval_variable, oval_resultDefinition; guard the reported attribute (oval_directives); guard a NULL frame from a duplicate variable id (oval_varModel).
  • XCCDF: NULL-safe atof() for empty lower/upper-bound and score elements (value.c, result.c); reject a NULL (benchmark.c); skip a with no @idref before strlen() (item.c); reject a NULL profile in tailoring (tailoring.c).
  • DS: NULL-safe id comparisons in sds_index / rds; reject a component with no id as a hash key (ds_rds_session.c).

Missing XML attributes/namespaces/text make libxml return NULL, which was
then passed to strcmp/atoi/atof or dereferenced. Guard these and make the
shared string helpers NULL-tolerant.

- common/util.h: oscap_str_startswith()/oscap_str_endswith() return false on
  a NULL string (matching oscap_strcmp/oscap_streq).
- OVAL: guard the optional `version` attribute before atoi() in oval_state,
  oval_definition, oval_object, oval_variable, oval_resultDefinition; guard
  the `reported` attribute (oval_directives); guard a NULL frame from a
  duplicate variable id (oval_varModel).
- XCCDF: NULL-safe atof() for empty lower/upper-bound and score elements
  (value.c, result.c); reject a NULL <TestResult> (benchmark.c); skip a
  <platform> with no @idref before strlen() (item.c); reject a NULL profile
  in tailoring (tailoring.c).
- DS: NULL-safe id comparisons in sds_index / rds; reject a component with no
  id as a hash key (ds_rds_session.c).
Copy link
Copy Markdown
Member

@Mab879 Mab879 left a comment

Choose a reason for hiding this comment

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

First of all, thank you for this series of PRs.

Please consider adding regression tests.

Comment thread src/DS/sds_index.c Outdated

int ret = 1;

if (s == NULL)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Shouldn't this be placed before line 457?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

oops, yep, thanks 😅 (fixed in 906022e)

Comment thread src/XCCDF/value.c Outdated
val = _xccdf_value_find_or_create_instance(XVALUE(value), selector, type);
val->lower_bound = atof(oscap_element_string_get(reader));
const char *lb = oscap_element_string_get(reader);
val->lower_bound = lb ? atof(lb) : 0.0;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Defaulting 0.0 might not be right thing here. Something like below might be better.

        if (lb == NULL) {
            dW("Empty <lower-bound> element is invalid, rejecting <Value>.");
            xccdf_value_free(value);
            return NULL;
        }
        val = _xccdf_value_find_or_create_instance(XVALUE(value), selector, type);
        val->lower_bound = atof(lb);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yea, I wasn't sure if it was preferable to default or to fail. sounds good to me :)
Applied your suggestion in 906022e as well

Comment thread src/XCCDF/value.c Outdated
val = _xccdf_value_find_or_create_instance(XVALUE(value), selector, type);
val->upper_bound = atof(oscap_element_string_get(reader));
const char *ub = oscap_element_string_get(reader);
val->upper_bound = ub ? atof(ub) : 0.0;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same as lower bound. Something like

   const char *ub = oscap_element_string_get(reader);
        if (ub == NULL) {
            dW("Empty <upper-bound> element is invalid, rejecting <Value>.");
            xccdf_value_free(value);
            return NULL;
        }
        val = _xccdf_value_find_or_create_instance(XVALUE(value), selector, type);
        val->upper_bound = atof(ub);

Comment thread src/XCCDF/result.c Outdated
else score->maximum = XCCDF_SCORE_MAX_DAFAULT;
score->score = atof(oscap_element_string_get(reader));
const char *score_str = oscap_element_string_get(reader);
score->score = score_str ? atof(score_str) : 0.0;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Something like might be better here.

if (score_str == NULL) {
    dW("Empty <score> element is invalid, rejecting.");
    xccdf_score_free(score);
    return NULL;
}
score->score = atof(score_str);

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Jun 4, 2026

@edznux-dd edznux-dd requested a review from Mab879 June 4, 2026 08:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants