Blame SOURCES/0013-libsepol-cil-Destroy-disabled-optional-blocks-after-.patch

060220
From d668f8e3a0a0361c03881ae3f00509196eaee064 Mon Sep 17 00:00:00 2001
060220
From: James Carter <jwcart2@gmail.com>
060220
Date: Mon, 8 Feb 2021 11:23:42 -0500
060220
Subject: [PATCH] libsepol/cil: Destroy disabled optional blocks after pass is
060220
 complete
060220
060220
Nicolas Iooss reports:
060220
  I am continuing to investigate OSS-Fuzz crashes and the following one
060220
  is quite complex. Here is a CIL policy which triggers a
060220
  heap-use-after-free error in the CIL compiler:
060220
060220
  (class CLASS (PERM2))
060220
  (classorder (CLASS))
060220
  (classpermission CLSPRM)
060220
  (optional o
060220
      (mlsvalidatetrans x (domby l1 h1))
060220
      (common CLSCOMMON (PERM1))
060220
      (classcommon CLASS CLSCOMMON)
060220
  )
060220
  (classpermissionset CLSPRM (CLASS (PERM1)))
060220
060220
  The issue is that the mlsvalidatetrans fails to resolve in pass
060220
  CIL_PASS_MISC3, which comes after the resolution of classcommon (in
060220
  pass CIL_PASS_MISC2). So:
060220
060220
  * In pass CIL_PASS_MISC2, the optional block still exists, the
060220
  classcommon is resolved and class CLASS is linked with common
060220
  CLSCOMMON.
060220
  * In pass CIL_PASS_MISC3, the optional block is destroyed, including
060220
  the common CLSCOMMON.
060220
  * When classpermissionset is resolved, function cil_resolve_classperms
060220
  uses "common_symtab = &class->common->perms;", which has been freed.
060220
  The use-after-free issue occurs in __cil_resolve_perms (in
060220
  libsepol/cil/src/cil_resolve_ast.c):
060220
060220
    // common_symtab was freed
060220
    rc = cil_symtab_get_datum(common_symtab, curr->data, &perm_datum);
060220
060220
The fundamental problem here is that when the optional block is
060220
disabled it is immediately destroyed in the middle of the pass, so
060220
the class has not been reset and still refers to the now destroyed
060220
common when the classpermissionset is resolved later in the same pass.
060220
060220
Added a list, disabled_optionals, to struct cil_args_resolve which is
060220
passed when resolving the tree. When optionals are disabled, they are
060220
now added to this list and then are destroyed after the tree has been
060220
reset between passes.
060220
060220
Reported-by: Nicolas Iooss <nicolas.iooss@m4x.org>
060220
Signed-off-by: James Carter <jwcart2@gmail.com>
060220
Acked-by: Nicolas Iooss <nicolas.iooss@m4x.org>
060220
---
060220
 libsepol/cil/src/cil_resolve_ast.c | 11 ++++++++++-
060220
 1 file changed, 10 insertions(+), 1 deletion(-)
060220
060220
diff --git a/libsepol/cil/src/cil_resolve_ast.c b/libsepol/cil/src/cil_resolve_ast.c
060220
index ed876260..979fa17d 100644
060220
--- a/libsepol/cil/src/cil_resolve_ast.c
060220
+++ b/libsepol/cil/src/cil_resolve_ast.c
060220
@@ -51,6 +51,7 @@ struct cil_args_resolve {
060220
 	struct cil_db *db;
060220
 	enum cil_pass pass;
060220
 	uint32_t *changed;
060220
+	struct cil_list *disabled_optionals;
060220
 	struct cil_tree_node *optstack;
060220
 	struct cil_tree_node *boolif;
060220
 	struct cil_tree_node *macro;
060220
@@ -3854,7 +3855,7 @@ int __cil_resolve_ast_last_child_helper(struct cil_tree_node *current, void *ext
060220
 
060220
 		if (((struct cil_optional *)parent->data)->enabled == CIL_FALSE) {
060220
 			*(args->changed) = CIL_TRUE;
060220
-			cil_tree_children_destroy(parent);
060220
+			cil_list_append(args->disabled_optionals, CIL_NODE, parent);
060220
 		}
060220
 
060220
 		/* pop off the stack */
060220
@@ -3917,6 +3918,7 @@ int cil_resolve_ast(struct cil_db *db, struct cil_tree_node *current)
060220
 	extra_args.in_list = NULL;
060220
 	extra_args.blockstack = NULL;
060220
 
060220
+	cil_list_init(&extra_args.disabled_optionals, CIL_NODE);
060220
 	cil_list_init(&extra_args.sidorder_lists, CIL_LIST_ITEM);
060220
 	cil_list_init(&extra_args.classorder_lists, CIL_LIST_ITEM);
060220
 	cil_list_init(&extra_args.unordered_classorder_lists, CIL_LIST_ITEM);
060220
@@ -3984,6 +3986,7 @@ int cil_resolve_ast(struct cil_db *db, struct cil_tree_node *current)
060220
 		}
060220
 
060220
 		if (changed && (pass > CIL_PASS_CALL1)) {
060220
+			struct cil_list_item *item;
060220
 			/* Need to re-resolve because an optional was disabled that contained
060220
 			 * one or more declarations. We only need to reset to the call1 pass 
060220
 			 * because things done in the preceeding passes aren't allowed in 
060220
@@ -4012,6 +4015,11 @@ int cil_resolve_ast(struct cil_db *db, struct cil_tree_node *current)
060220
 				cil_log(CIL_ERR, "Failed to reset declarations\n");
060220
 				goto exit;
060220
 			}
060220
+			cil_list_for_each(item, extra_args.disabled_optionals) {
060220
+				cil_tree_children_destroy(item->data);
060220
+			}
060220
+			cil_list_destroy(&extra_args.disabled_optionals, CIL_FALSE);
060220
+			cil_list_init(&extra_args.disabled_optionals, CIL_NODE);
060220
 		}
060220
 
060220
 		/* reset the arguments */
060220
@@ -4040,6 +4048,7 @@ exit:
060220
 	__cil_ordered_lists_destroy(&extra_args.catorder_lists);
060220
 	__cil_ordered_lists_destroy(&extra_args.sensitivityorder_lists);
060220
 	__cil_ordered_lists_destroy(&extra_args.unordered_classorder_lists);
060220
+	cil_list_destroy(&extra_args.disabled_optionals, CIL_FALSE);
060220
 	cil_list_destroy(&extra_args.in_list, CIL_FALSE);
060220
 
060220
 	return rc;
060220
-- 
060220
2.30.2
060220