This is an issue with the scanner itself, not with SCAP Workbench, SCAP Security Guide or the Anaconda
addon. It concerns how oscap evaluates a state, not the content being evaluated.
Description of Problem:
When an OVAL state contains no entities, eval_item() records no results for it and
ores_get_result_byopr() maps the empty result set to OVAL_RESULT_UNKNOWN. A state which imposes no
conditions is satisfied by any item, so this should be true.
This is not a theoretical edge case. Canonical's Ubuntu OVAL feeds now ship the singleton
family_state with no entities, so the family test in every Ubuntu inventory definition returns
unknown. Because each USN definition ANDs an applicability check against that inventory definition,
no USN definition can ever evaluate to true: those which should be true become unknown, and the
scan reports no vulnerabilities at all while exiting 0.
The result is a silent false negative for anyone scanning Ubuntu with the feeds from
https://security-metadata.canonical.com/oval/. I have reported the feed change to Canonical as well
(https://bugs.launchpad.net/ubuntu-cve-tracker/+bug/2161980, filed independently by another reporter),
but the handling of an entity-less state looks wrong regardless of what they do, and no released
OpenSCAP version behaves differently.
OpenSCAP Version:
Reproduced on 1.4.4, built from the release tarball.
Operating System & Version:
Scanner running on Ubuntu 22.04 (jammy), x86_64.
Targets are mounted Ubuntu 18.04 (bionic) and 22.04 (jammy) disk images, scanned offline via
OSCAP_PROBE_ROOT. The reproducer below needs no target system at all.
Steps to Reproduce:
- Save this as
empty-state-repro.oval.xml. def:1 uses an entity-less family_state; def:2 is
the identical test against a state which names the family, as a control.
<?xml version="1.0" encoding="UTF-8"?>
<oval_definitions xmlns="http://oval.mitre.org/XMLSchema/oval-definitions-5"
xmlns:oval="http://oval.mitre.org/XMLSchema/oval-common-5"
xmlns:ind="http://oval.mitre.org/XMLSchema/oval-definitions-5#independent"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<generator>
<oval:product_name>empty family_state reproducer</oval:product_name>
<oval:schema_version>5.11.1</oval:schema_version>
<oval:timestamp>2026-07-28T00:00:00</oval:timestamp>
</generator>
<definitions>
<definition class="inventory" id="oval:repro:def:1" version="1">
<metadata>
<title>family_state with no entities</title>
<description>Should be true: a state with no entities imposes no conditions.</description>
</metadata>
<criteria>
<criterion test_ref="oval:repro:tst:1" comment="family test against an entity-less state"/>
</criteria>
</definition>
<definition class="inventory" id="oval:repro:def:2" version="1">
<metadata>
<title>family_state with one entity (control)</title>
<description>Control case: the same test against a state which names the family.</description>
</metadata>
<criteria>
<criterion test_ref="oval:repro:tst:2" comment="family test against a populated state"/>
</criteria>
</definition>
</definitions>
<tests>
<ind:family_test id="oval:repro:tst:1" version="1" check="at least one"
check_existence="at_least_one_exists" comment="entity-less state">
<ind:object object_ref="oval:repro:obj:1"/>
<ind:state state_ref="oval:repro:ste:1"/>
</ind:family_test>
<ind:family_test id="oval:repro:tst:2" version="1" check="at least one"
check_existence="at_least_one_exists" comment="populated state">
<ind:object object_ref="oval:repro:obj:1"/>
<ind:state state_ref="oval:repro:ste:2"/>
</ind:family_test>
</tests>
<objects>
<ind:family_object id="oval:repro:obj:1" version="1" comment="The singleton family object"/>
</objects>
<states>
<ind:family_state id="oval:repro:ste:1" version="1" comment="no entities"/>
<ind:family_state id="oval:repro:ste:2" version="1" comment="control">
<ind:family>unix</ind:family>
</ind:family_state>
</states>
</oval_definitions>
- Confirm the document is valid, which it is.
oscap oval validate runs Schematron by default in
1.4.4 and exits 0 with no findings:
oscap oval validate empty-state-repro.oval.xml
- Evaluate it. No probe root or privileges are needed:
oscap oval eval empty-state-repro.oval.xml
Actual Results:
Definition oval:repro:def:2: true
Definition oval:repro:def:1: unknown
Evaluation done.
Expected Results:
Both definitions true.
Additional Information / Debugging Steps:
Cause. In src/OVAL/results/oval_resultTest.c, eval_item() iterates the state's contents and
accumulates one result per entity:
ores_clear(&ste_ores);
state_contents_itr = oval_state_get_contents(state);
while (oval_state_content_iterator_has_more(state_contents_itr)) {
...
ores_add_res(&ste_ores, ste_ent_res);
}
...
operator = oval_state_get_operator(state);
result = ores_get_result_byopr(&ste_ores, operator);
With no entities the loop body never executes, so every counter stays at zero and
ores_get_result_byopr() returns before the operator is considered:
if (ores->true_cnt == 0 &&
ores->false_cnt == 0 &&
ores->error_cnt == 0 &&
ores->unknown_cnt == 0 &&
ores->notappl_cnt == 0 &&
ores->noteval_cnt == 0)
return OVAL_RESULT_UNKNOWN;
"Nothing to evaluate" is being conflated with "cannot determine the answer".
Why true is the correct result. The OVAL 5.11.1 schema documentation for StateType says:
When evaluating a particular state against an object, one should evaluate each individual entity
separately. The individual results are then combined by the operator to produce an overall result.
The state operator defaults to AND, and an AND over an empty set is vacuously true. The schema
permits the empty case explicitly (family_state declares its entity minOccurs="0"), and no
Schematron rule requires a state to carry entities, so content like this is legal and an implementation
has to decide what it means. unknown is the one answer that makes the document unusable.
Impact in practice. Ubuntu USN definitions have the shape:
definition (class="patch")
+- criteria (AND)
+- extend_definition -> inventory def:100, applicability_check="true"
| +- criteria (AND)
| +- criterion tst:100 family_test <-- unknown
| +- criterion tst:101 textfilecontent54 <-- true on a real Ubuntu system
+- criterion tst:NNN dpkginfo_test <-- the actual version comparison
Given AND semantics where false dominates and unknown blocks true:
def:100 becomes AND(unknown, true) = unknown
- a USN definition whose package test is false stays
false (the unknown is masked)
- a USN definition whose package test is true becomes
unknown
so a vulnerable system reports nothing. Measured on one Ubuntu 18.04 image, same binary, same package
set, only the feed differing:
| Feed |
family_test |
true |
false |
unknown |
exit |
generated 2024-10-29, with <family>unix</family> |
true |
318 |
1980 |
0 |
0 |
| current, entity-less state |
unknown |
0 |
2560 |
393 |
0 |
The 393 unknown definitions are exactly the vulnerabilities that should have been reported: 406 USNs
and 1870 CVEs on that image became zero. Note the exit code is 0 in both cases, and the HTML report
shows a fail count of 0 alongside an unknown count of 393, so nothing signals that the scan found
nothing because it could not evaluate anything.
Suggested fix. Return true when the state has no contents, leaving ores_get_result_byopr() alone
since it is shared with criteria combination:
state_contents_itr = oval_state_get_contents(state);
if (!oval_state_content_iterator_has_more(state_contents_itr)) {
/* A state with no entities places no conditions on the item, so every
* item satisfies it. */
oval_state_content_iterator_free(state_contents_itr);
return OVAL_RESULT_TRUE;
}
while (oval_state_content_iterator_has_more(state_contents_itr)) {
With this applied, def:1 above evaluates to true, and the Ubuntu bionic feed goes from 0 true / 393
unknown back to 393 true / 0 unknown.
Note this deliberately does not make the test pass when nothing was collected. The guard is inside the
per-item comparison, which only runs when an item exists, so check_existence still decides that case.
Verified with an entity-less dpkginfo_state against an absent package: the test is false, with no
tested items, while the same state against an installed package gives true.
This is an issue with the scanner itself, not with SCAP Workbench, SCAP Security Guide or the Anaconda
addon. It concerns how
oscapevaluates a state, not the content being evaluated.Description of Problem:
When an OVAL state contains no entities,
eval_item()records no results for it andores_get_result_byopr()maps the empty result set toOVAL_RESULT_UNKNOWN. A state which imposes noconditions is satisfied by any item, so this should be
true.This is not a theoretical edge case. Canonical's Ubuntu OVAL feeds now ship the singleton
family_statewith no entities, so the family test in every Ubuntu inventory definition returnsunknown. Because each USN definition ANDs an applicability check against that inventory definition,no USN definition can ever evaluate to
true: those which should betruebecomeunknown, and thescan reports no vulnerabilities at all while exiting 0.
The result is a silent false negative for anyone scanning Ubuntu with the feeds from
https://security-metadata.canonical.com/oval/. I have reported the feed change to Canonical as well(https://bugs.launchpad.net/ubuntu-cve-tracker/+bug/2161980, filed independently by another reporter),
but the handling of an entity-less state looks wrong regardless of what they do, and no released
OpenSCAP version behaves differently.
OpenSCAP Version:
Reproduced on 1.4.4, built from the release tarball.
Operating System & Version:
Scanner running on Ubuntu 22.04 (jammy), x86_64.
Targets are mounted Ubuntu 18.04 (bionic) and 22.04 (jammy) disk images, scanned offline via
OSCAP_PROBE_ROOT. The reproducer below needs no target system at all.Steps to Reproduce:
empty-state-repro.oval.xml.def:1uses an entity-lessfamily_state;def:2isthe identical test against a state which names the family, as a control.
oscap oval validateruns Schematron by default in1.4.4 and exits 0 with no findings:
Actual Results:
Expected Results:
Both definitions
true.Additional Information / Debugging Steps:
Cause. In
src/OVAL/results/oval_resultTest.c,eval_item()iterates the state's contents andaccumulates one result per entity:
With no entities the loop body never executes, so every counter stays at zero and
ores_get_result_byopr()returns before the operator is considered:"Nothing to evaluate" is being conflated with "cannot determine the answer".
Why
trueis the correct result. The OVAL 5.11.1 schema documentation forStateTypesays:The state operator defaults to
AND, and anANDover an empty set is vacuously true. The schemapermits the empty case explicitly (
family_statedeclares its entityminOccurs="0"), and noSchematron rule requires a state to carry entities, so content like this is legal and an implementation
has to decide what it means.
unknownis the one answer that makes the document unusable.Impact in practice. Ubuntu USN definitions have the shape:
Given
ANDsemantics where false dominates and unknown blocks true:def:100becomesAND(unknown, true)=unknownfalse(the unknown is masked)unknownso a vulnerable system reports nothing. Measured on one Ubuntu 18.04 image, same binary, same package
set, only the feed differing:
<family>unix</family>The 393
unknowndefinitions are exactly the vulnerabilities that should have been reported: 406 USNsand 1870 CVEs on that image became zero. Note the exit code is 0 in both cases, and the HTML report
shows a fail count of 0 alongside an unknown count of 393, so nothing signals that the scan found
nothing because it could not evaluate anything.
Suggested fix. Return true when the state has no contents, leaving
ores_get_result_byopr()alonesince it is shared with criteria combination:
With this applied,
def:1above evaluates totrue, and the Ubuntu bionic feed goes from 0 true / 393unknown back to 393 true / 0 unknown.
Note this deliberately does not make the test pass when nothing was collected. The guard is inside the
per-item comparison, which only runs when an item exists, so
check_existencestill decides that case.Verified with an entity-less
dpkginfo_stateagainst an absent package: the test isfalse, with notested items, while the same state against an installed package gives
true.