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

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