Blob Blame History Raw
From 97ea3d5dd72e11996fac63bfdcf9691e1515f09a Mon Sep 17 00:00:00 2001
From: Malte Kraus <malte.kraus@suse.com>
Date: Fri, 29 Mar 2019 16:12:22 +0100
Subject: [PATCH] improve --stig-viewer output when there is no 1:1 connection
 between rules

There can both be rules covering multiple STIG IDs (easy to handle: just
output the same result multiple times), and STIG IDs covered by more
than one rule. The DISA Stig Viewer seems to only consider the last
result for a given rule when it imports results, so when that last rule
happens to pass, that means the whole STIG will be marked as 'not a
finding'. To counteract that, this commit attempts to merge the results
in a sensible way - only if all tests passed, the STIG should be
marked as 'not a finding' in the way.
---
 src/XCCDF/result.c     | 92 +++++++++++++++++++++++++++++++++---------
 src/XCCDF/xccdf_impl.h |  3 +-
 2 files changed, 76 insertions(+), 19 deletions(-)

diff --git a/src/XCCDF/result.c b/src/XCCDF/result.c
index f389813d5..f0fa647b2 100644
--- a/src/XCCDF/result.c
+++ b/src/XCCDF/result.c
@@ -924,13 +924,68 @@ void xccdf_result_to_dom(struct xccdf_result *result, xmlNode *result_node, xmlD
 	}
 	xccdf_setvalue_iterator_free(setvalues);
 
+	struct oscap_htable *nodes_by_rule_id = oscap_htable_new();
+
 	struct xccdf_rule_result_iterator *rule_results = xccdf_result_get_rule_results(result);
+		if (use_stig_rule_id) {
+		while (xccdf_rule_result_iterator_has_more(rule_results)) {
+			struct xccdf_rule_result *rule_result = xccdf_rule_result_iterator_next(rule_results);
+
+			const char *idref = xccdf_rule_result_get_idref(rule_result);
+			if (!idref)
+				continue;
+
+			xccdf_test_result_type_t test_res = xccdf_rule_result_get_result(rule_result);
+
+			const struct xccdf_item *item = xccdf_benchmark_get_member(associated_benchmark, XCCDF_RULE, idref);
+			if (!item)
+				continue;
+
+			struct oscap_reference_iterator *references = xccdf_item_get_references(item);
+			while (oscap_reference_iterator_has_more(references)) {
+				struct oscap_reference *ref = oscap_reference_iterator_next(references);
+				if (strcmp(oscap_reference_get_href(ref), DISA_STIG_VIEWER_HREF) == 0) {
+					const char *stig_rule_id = oscap_reference_get_title(ref);
+
+					xccdf_test_result_type_t other_res = (xccdf_test_result_type_t)oscap_htable_detach(nodes_by_rule_id, stig_rule_id);
+					xccdf_test_result_type_t wanted_res;
+					if (other_res == 0) {
+						wanted_res = test_res;
+					} else {
+						// if one test passed, and the other didn't, the other one should win
+						if (test_res == XCCDF_RESULT_PASS) {
+							wanted_res = other_res;
+						} else if (other_res == XCCDF_RESULT_PASS) {
+							wanted_res = test_res;
+						// if one had an error, that should win
+						} else if (test_res == XCCDF_RESULT_ERROR || other_res == XCCDF_RESULT_ERROR) {
+							wanted_res = XCCDF_RESULT_ERROR;
+						// next prio: failures
+						} else if (test_res == XCCDF_RESULT_FAIL || other_res == XCCDF_RESULT_FAIL) {
+							wanted_res = XCCDF_RESULT_FAIL;
+						// next prio: unknown
+						} else if (test_res == XCCDF_RESULT_UNKNOWN || other_res == XCCDF_RESULT_UNKNOWN) {
+							wanted_res = XCCDF_RESULT_UNKNOWN;
+						// otherwise, just pick the lower one (more or less arbitrarily)
+						} else {
+							wanted_res = (test_res < other_res) ? test_res : other_res;
+						}
+					}
+					oscap_htable_add(nodes_by_rule_id, stig_rule_id, (void*)wanted_res);
+				}
+			}
+			oscap_reference_iterator_free(references);
+		}
+		xccdf_rule_result_iterator_reset(rule_results);
+	}
 	while (xccdf_rule_result_iterator_has_more(rule_results)) {
 		struct xccdf_rule_result *rule_result = xccdf_rule_result_iterator_next(rule_results);
-		xccdf_rule_result_to_dom(rule_result, doc, result_node, version_info, associated_benchmark, use_stig_rule_id);
+		xccdf_rule_result_to_dom(rule_result, doc, result_node, version_info, associated_benchmark, use_stig_rule_id, nodes_by_rule_id);
 	}
 	xccdf_rule_result_iterator_free(rule_results);
 
+	oscap_htable_free0(nodes_by_rule_id);
+
 	struct xccdf_score_iterator *scores = xccdf_result_get_scores(result);
 	while (xccdf_score_iterator_has_more(scores)) {
 		struct xccdf_score *score = xccdf_score_iterator_next(scores);
@@ -1081,36 +1136,40 @@ xmlNode *xccdf_target_identifier_to_dom(const struct xccdf_target_identifier *ti
 	}
 }
 
-xmlNode *xccdf_rule_result_to_dom(struct xccdf_rule_result *result, xmlDoc *doc, xmlNode *parent, const struct xccdf_version_info* version_info, struct xccdf_benchmark *benchmark, bool use_stig_rule_id)
+static void _xccdf_rule_result_to_dom_idref(struct xccdf_rule_result *result, xmlDoc *doc, xmlNode *parent, const struct xccdf_version_info* version_info, struct xccdf_benchmark *benchmark, const char *idref);
+void xccdf_rule_result_to_dom(struct xccdf_rule_result *result, xmlDoc *doc, xmlNode *parent, const struct xccdf_version_info* version_info, struct xccdf_benchmark *benchmark, bool use_stig_rule_id, struct oscap_htable *nodes_by_rule_id)
 {
 	const char *idref = xccdf_rule_result_get_idref(result);
 	if (use_stig_rule_id) {
 		// Don't output rules with no stig ids
 		if (!idref || !benchmark)
-			return NULL;
+			return;
 
-		struct xccdf_item *item = xccdf_benchmark_get_member(benchmark, XCCDF_RULE, idref);
+		const struct xccdf_item *item = xccdf_benchmark_get_member(benchmark, XCCDF_RULE, idref);
 		if (!item)
-			return NULL;
+			return;
 
-		const char *stig_rule_id = NULL;		
-		struct oscap_reference_iterator *references = xccdf_item_get_references(XRULE(item));
+		struct oscap_reference_iterator *references = xccdf_item_get_references(item);
 		while (oscap_reference_iterator_has_more(references)) {
 			struct oscap_reference *ref = oscap_reference_iterator_next(references);
 			if (strcmp(oscap_reference_get_href(ref), DISA_STIG_VIEWER_HREF[0]) == 0 ||
 			    strcmp(oscap_reference_get_href(ref), DISA_STIG_VIEWER_HREF[1]) == 0) {
-				stig_rule_id = oscap_reference_get_title(ref);
-				break;
+				const char *stig_rule_id = oscap_reference_get_title(ref);
+
+				xccdf_test_result_type_t expected_res = (xccdf_test_result_type_t)oscap_htable_get(nodes_by_rule_id, stig_rule_id);
+				xccdf_test_result_type_t test_res = xccdf_rule_result_get_result(result);
+				if (expected_res == test_res) {
+					oscap_htable_detach(nodes_by_rule_id, stig_rule_id);
+					_xccdf_rule_result_to_dom_idref(result, doc, parent, version_info, benchmark, stig_rule_id);
+				}
 			}
 		}
 		oscap_reference_iterator_free(references);
-
-		if (!stig_rule_id)
-			return NULL;
-
-		idref = stig_rule_id;
+	} else {
+		_xccdf_rule_result_to_dom_idref(result, doc, parent, version_info, benchmark, idref);
 	}
-
+}
+static void _xccdf_rule_result_to_dom_idref(struct xccdf_rule_result *result, xmlDoc *doc, xmlNode *parent, const struct xccdf_version_info* version_info, struct xccdf_benchmark *benchmark, const char *idref) {
 	xmlNs *ns_xccdf = lookup_xccdf_ns(doc, parent, version_info);
 
 	xmlNode *result_node = xmlNewTextChild(parent, ns_xccdf, BAD_CAST "rule-result", NULL);
@@ -1203,8 +1262,6 @@ xmlNode *xccdf_rule_result_to_dom(struct xccdf_rule_result *result, xmlDoc *doc,
 		xccdf_check_to_dom(check, doc, result_node, version_info);
 	}
 	xccdf_check_iterator_free(checks);
-
-	return result_node;
 }
 
 bool xccdf_rule_result_override(struct xccdf_rule_result *rule_result, xccdf_test_result_type_t new_result, const char *waiver_time, const char *authority, struct oscap_text *remark)
diff --git a/src/XCCDF/xccdf_impl.h b/src/XCCDF/xccdf_impl.h
index b87e7f170..05e0edd85 100644
--- a/src/XCCDF/xccdf_impl.h
+++ b/src/XCCDF/xccdf_impl.h
@@ -27,6 +27,7 @@
 #include <public/xccdf_benchmark.h>
 #include <common/util.h>
 #include <libxml/xmlreader.h>
+#include <common/list.h>
 
 OSCAP_HIDDEN_START;
 
@@ -49,7 +50,7 @@ void xccdf_group_to_dom(struct xccdf_group *group, xmlNode *group_node, xmlDoc *
 void xccdf_profile_to_dom(struct xccdf_profile *profile, xmlNode *profile_node, xmlDoc *doc, xmlNode *parent, const struct xccdf_version_info *version_info);
 void xccdf_result_to_dom(struct xccdf_result *result, xmlNode *result_node, xmlDoc *doc, xmlNode *parent, bool use_stig_rule_id);
 xmlNode *xccdf_target_identifier_to_dom(const struct xccdf_target_identifier *ti, xmlDoc *doc, xmlNode *parent, const struct xccdf_version_info* version_info);
-xmlNode *xccdf_rule_result_to_dom(struct xccdf_rule_result *result, xmlDoc *doc, xmlNode *parent, const struct xccdf_version_info* version_info, struct xccdf_benchmark *benchmark, bool use_stig_rule_id);
+void xccdf_rule_result_to_dom(struct xccdf_rule_result *result, xmlDoc *doc, xmlNode *parent, const struct xccdf_version_info* version_info, struct xccdf_benchmark *benchmark, bool use_stig_rule_id, struct oscap_htable *nodes_by_rule_id);
 xmlNode *xccdf_ident_to_dom(struct xccdf_ident *ident, xmlDoc *doc, xmlNode *parent, const struct xccdf_version_info* version_info);
 xmlNode *xccdf_setvalue_to_dom(struct xccdf_setvalue *setvalue, xmlDoc *doc, xmlNode *parent, const struct xccdf_version_info* version_info);
 xmlNode *xccdf_override_to_dom(struct xccdf_override *override, xmlDoc *doc, xmlNode *parent, const struct xccdf_version_info* version_info);