ddd80ee
From 4039d67b1e3b235b5fdc90cd70c907711fe5cd22 Mon Sep 17 00:00:00 2001
ddd80ee
From: Petr Lautrbach <plautrba@redhat.com>
ddd80ee
Date: Mon, 17 Feb 2020 21:47:35 +0100
ddd80ee
Subject: [PATCH] libselinux: Eliminate use of security_compute_user()
ddd80ee
ddd80ee
get_ordered_context_list() code used to ask the kernel to compute the complete
ddd80ee
set of reachable contexts using /sys/fs/selinux/user aka
ddd80ee
security_compute_user(). This set can be so huge so that it doesn't fit into a
ddd80ee
kernel page and security_compute_user() fails. Even if it doesn't fail,
ddd80ee
get_ordered_context_list() throws away the vast majority of the returned
ddd80ee
contexts because they don't match anything in
ddd80ee
/etc/selinux/targeted/contexts/default_contexts or
ddd80ee
/etc/selinux/targeted/contexts/users/
ddd80ee
ddd80ee
get_ordered_context_list() is rewritten to compute set of contexts based on
ddd80ee
/etc/selinux/targeted/contexts/users/ and
ddd80ee
/etc/selinux/targeted/contexts/default_contexts files and to return only valid
ddd80ee
contexts, using security_check_context(), from this set.
ddd80ee
ddd80ee
Fixes: https://github.com/SELinuxProject/selinux/issues/28
ddd80ee
ddd80ee
Signed-off-by: Petr Lautrbach <plautrba@redhat.com>
ddd80ee
---
ddd80ee
 libselinux/src/get_context_list.c | 212 +++++++++++++-----------------
ddd80ee
 1 file changed, 93 insertions(+), 119 deletions(-)
ddd80ee
ddd80ee
diff --git a/libselinux/src/get_context_list.c b/libselinux/src/get_context_list.c
ddd80ee
index 689e46589f30..26d7b3b98e75 100644
ddd80ee
--- a/libselinux/src/get_context_list.c
ddd80ee
+++ b/libselinux/src/get_context_list.c
ddd80ee
@@ -2,6 +2,7 @@
ddd80ee
 #include <errno.h>
ddd80ee
 #include <stdio.h>
ddd80ee
 #include <stdio_ext.h>
ddd80ee
+#include <stdint.h>
ddd80ee
 #include <stdlib.h>
ddd80ee
 #include <string.h>
ddd80ee
 #include <ctype.h>
ddd80ee
@@ -114,64 +115,41 @@ int get_default_context(const char *user,
ddd80ee
 	return 0;
ddd80ee
 }
ddd80ee
 
ddd80ee
-static int find_partialcon(char ** list,
ddd80ee
-			   unsigned int nreach, char *part)
ddd80ee
+static int is_in_reachable(char **reachable, const char *usercon_str)
ddd80ee
 {
ddd80ee
-	const char *conrole, *contype;
ddd80ee
-	char *partrole, *parttype, *ptr;
ddd80ee
-	context_t con;
ddd80ee
-	unsigned int i;
ddd80ee
+	if (!reachable)
ddd80ee
+		return 0;
ddd80ee
 
ddd80ee
-	partrole = part;
ddd80ee
-	ptr = part;
ddd80ee
-	while (*ptr && !isspace(*ptr) && *ptr != ':')
ddd80ee
-		ptr++;
ddd80ee
-	if (*ptr != ':')
ddd80ee
-		return -1;
ddd80ee
-	*ptr++ = 0;
ddd80ee
-	parttype = ptr;
ddd80ee
-	while (*ptr && !isspace(*ptr) && *ptr != ':')
ddd80ee
-		ptr++;
ddd80ee
-	*ptr = 0;
ddd80ee
-
ddd80ee
-	for (i = 0; i < nreach; i++) {
ddd80ee
-		con = context_new(list[i]);
ddd80ee
-		if (!con)
ddd80ee
-			return -1;
ddd80ee
-		conrole = context_role_get(con);
ddd80ee
-		contype = context_type_get(con);
ddd80ee
-		if (!conrole || !contype) {
ddd80ee
-			context_free(con);
ddd80ee
-			return -1;
ddd80ee
-		}
ddd80ee
-		if (!strcmp(conrole, partrole) && !strcmp(contype, parttype)) {
ddd80ee
-			context_free(con);
ddd80ee
-			return i;
ddd80ee
+	for (; *reachable != NULL; reachable++) {
ddd80ee
+		if (strcmp(*reachable, usercon_str) == 0) {
ddd80ee
+			return 1;
ddd80ee
 		}
ddd80ee
-		context_free(con);
ddd80ee
 	}
ddd80ee
-
ddd80ee
-	return -1;
ddd80ee
+	return 0;
ddd80ee
 }
ddd80ee
 
ddd80ee
-static int get_context_order(FILE * fp,
ddd80ee
+static int get_context_user(FILE * fp,
ddd80ee
 			     char * fromcon,
ddd80ee
-			     char ** reachable,
ddd80ee
-			     unsigned int nreach,
ddd80ee
-			     unsigned int *ordering, unsigned int *nordered)
ddd80ee
+			     const char * user,
ddd80ee
+			     char ***reachable,
ddd80ee
+			     unsigned int *nreachable)
ddd80ee
 {
ddd80ee
 	char *start, *end = NULL;
ddd80ee
 	char *line = NULL;
ddd80ee
-	size_t line_len = 0;
ddd80ee
+	size_t line_len = 0, usercon_len;
ddd80ee
+	size_t user_len = strlen(user);
ddd80ee
 	ssize_t len;
ddd80ee
 	int found = 0;
ddd80ee
-	const char *fromrole, *fromtype;
ddd80ee
+	const char *fromrole, *fromtype, *fromlevel;
ddd80ee
 	char *linerole, *linetype;
ddd80ee
-	unsigned int i;
ddd80ee
+	char **new_reachable = NULL;
ddd80ee
+	char *usercon_str;
ddd80ee
 	context_t con;
ddd80ee
+	context_t usercon;
ddd80ee
+
ddd80ee
 	int rc;
ddd80ee
 
ddd80ee
-	errno = -EINVAL;
ddd80ee
+	errno = EINVAL;
ddd80ee
 
ddd80ee
 	/* Extract the role and type of the fromcon for matching.
ddd80ee
 	   User identity and MLS range can be variable. */
ddd80ee
@@ -180,6 +158,7 @@ static int get_context_order(FILE * fp,
ddd80ee
 		return -1;
ddd80ee
 	fromrole = context_role_get(con);
ddd80ee
 	fromtype = context_type_get(con);
ddd80ee
+	fromlevel = context_range_get(con);
ddd80ee
 	if (!fromrole || !fromtype) {
ddd80ee
 		context_free(con);
ddd80ee
 		return -1;
ddd80ee
@@ -243,23 +222,75 @@ static int get_context_order(FILE * fp,
ddd80ee
 		if (*end)
ddd80ee
 			*end++ = 0;
ddd80ee
 
ddd80ee
-		/* Check for a match in the reachable list. */
ddd80ee
-		rc = find_partialcon(reachable, nreach, start);
ddd80ee
-		if (rc < 0) {
ddd80ee
-			/* No match, skip it. */
ddd80ee
+		/* Check whether a new context is valid */
ddd80ee
+		if (SIZE_MAX - user_len < strlen(start) + 2) {
ddd80ee
+			fprintf(stderr, "%s: one of partial contexts is too big\n", __FUNCTION__);
ddd80ee
+			errno = EINVAL;
ddd80ee
+			rc = -1;
ddd80ee
+			goto out;
ddd80ee
+		}
ddd80ee
+		usercon_len = user_len + strlen(start) + 2;
ddd80ee
+		usercon_str = malloc(usercon_len);
ddd80ee
+		if (!usercon_str) {
ddd80ee
+			rc = -1;
ddd80ee
+			goto out;
ddd80ee
+		}
ddd80ee
+
ddd80ee
+		/* set range from fromcon in the new usercon */
ddd80ee
+		snprintf(usercon_str, usercon_len, "%s:%s", user, start);
ddd80ee
+		usercon = context_new(usercon_str);
ddd80ee
+		if (!usercon) {
ddd80ee
+			if (errno != EINVAL) {
ddd80ee
+				free(usercon_str);
ddd80ee
+				rc = -1;
ddd80ee
+				goto out;
ddd80ee
+			}
ddd80ee
+			fprintf(stderr,
ddd80ee
+				"%s: can't create a context from %s, skipping\n",
ddd80ee
+				__FUNCTION__, usercon_str);
ddd80ee
+			free(usercon_str);
ddd80ee
 			start = end;
ddd80ee
 			continue;
ddd80ee
 		}
ddd80ee
+		free(usercon_str);
ddd80ee
+		if (context_range_set(usercon, fromlevel) != 0) {
ddd80ee
+			context_free(usercon);
ddd80ee
+			rc = -1;
ddd80ee
+			goto out;
ddd80ee
+		}
ddd80ee
+		usercon_str = context_str(usercon);
ddd80ee
+		if (!usercon_str) {
ddd80ee
+			context_free(usercon);
ddd80ee
+			rc = -1;
ddd80ee
+			goto out;
ddd80ee
+		}
ddd80ee
 
ddd80ee
-		/* If a match is found and the entry is not already ordered
ddd80ee
-		   (e.g. due to prior match in prior config file), then set
ddd80ee
-		   the ordering for it. */
ddd80ee
-		i = rc;
ddd80ee
-		if (ordering[i] == nreach)
ddd80ee
-			ordering[i] = (*nordered)++;
ddd80ee
+		/* check whether usercon is already in reachable */
ddd80ee
+		if (is_in_reachable(*reachable, usercon_str)) {
ddd80ee
+			context_free(usercon);
ddd80ee
+			start = end;
ddd80ee
+			continue;
ddd80ee
+		}
ddd80ee
+		if (security_check_context(usercon_str) == 0) {
ddd80ee
+			new_reachable = realloc(*reachable, (*nreachable + 2) * sizeof(char *));
ddd80ee
+			if (!new_reachable) {
ddd80ee
+				context_free(usercon);
ddd80ee
+				rc = -1;
ddd80ee
+				goto out;
ddd80ee
+			}
ddd80ee
+			*reachable = new_reachable;
ddd80ee
+			new_reachable[*nreachable] = strdup(usercon_str);
ddd80ee
+			if (new_reachable[*nreachable] == NULL) {
ddd80ee
+				context_free(usercon);
ddd80ee
+				rc = -1;
ddd80ee
+				goto out;
ddd80ee
+			}
ddd80ee
+			new_reachable[*nreachable + 1] = 0;
ddd80ee
+			*nreachable += 1;
ddd80ee
+		}
ddd80ee
+		context_free(usercon);
ddd80ee
 		start = end;
ddd80ee
 	}
ddd80ee
-
ddd80ee
 	rc = 0;
ddd80ee
 
ddd80ee
       out:
ddd80ee
@@ -313,21 +344,6 @@ static int get_failsafe_context(const char *user, char ** newcon)
ddd80ee
 	return 0;
ddd80ee
 }
ddd80ee
 
ddd80ee
-struct context_order {
ddd80ee
-	char * con;
ddd80ee
-	unsigned int order;
ddd80ee
-};
ddd80ee
-
ddd80ee
-static int order_compare(const void *A, const void *B)
ddd80ee
-{
ddd80ee
-	const struct context_order *c1 = A, *c2 = B;
ddd80ee
-	if (c1->order < c2->order)
ddd80ee
-		return -1;
ddd80ee
-	else if (c1->order > c2->order)
ddd80ee
-		return 1;
ddd80ee
-	return strcmp(c1->con, c2->con);
ddd80ee
-}
ddd80ee
-
ddd80ee
 int get_ordered_context_list_with_level(const char *user,
ddd80ee
 					const char *level,
ddd80ee
 					char * fromcon,
ddd80ee
@@ -395,11 +411,8 @@ int get_ordered_context_list(const char *user,
ddd80ee
 			     char *** list)
ddd80ee
 {
ddd80ee
 	char **reachable = NULL;
ddd80ee
-	unsigned int *ordering = NULL;
ddd80ee
-	struct context_order *co = NULL;
ddd80ee
-	char **ptr;
ddd80ee
 	int rc = 0;
ddd80ee
-	unsigned int nreach = 0, nordered = 0, freefrom = 0, i;
ddd80ee
+	unsigned nreachable = 0, freefrom = 0;
ddd80ee
 	FILE *fp;
ddd80ee
 	char *fname = NULL;
ddd80ee
 	size_t fname_len;
ddd80ee
@@ -413,23 +426,6 @@ int get_ordered_context_list(const char *user,
ddd80ee
 		freefrom = 1;
ddd80ee
 	}
ddd80ee
 
ddd80ee
-	/* Determine the set of reachable contexts for the user. */
ddd80ee
-	rc = security_compute_user(fromcon, user, &reachable);
ddd80ee
-	if (rc < 0)
ddd80ee
-		goto failsafe;
ddd80ee
-	nreach = 0;
ddd80ee
-	for (ptr = reachable; *ptr; ptr++)
ddd80ee
-		nreach++;
ddd80ee
-	if (!nreach)
ddd80ee
-		goto failsafe;
ddd80ee
-
ddd80ee
-	/* Initialize ordering array. */
ddd80ee
-	ordering = malloc(nreach * sizeof(unsigned int));
ddd80ee
-	if (!ordering)
ddd80ee
-		goto failsafe;
ddd80ee
-	for (i = 0; i < nreach; i++)
ddd80ee
-		ordering[i] = nreach;
ddd80ee
-
ddd80ee
 	/* Determine the ordering to apply from the optional per-user config
ddd80ee
 	   and from the global config. */
ddd80ee
 	fname_len = strlen(user_contexts_path) + strlen(user) + 2;
ddd80ee
@@ -440,8 +436,8 @@ int get_ordered_context_list(const char *user,
ddd80ee
 	fp = fopen(fname, "re");
ddd80ee
 	if (fp) {
ddd80ee
 		__fsetlocking(fp, FSETLOCKING_BYCALLER);
ddd80ee
-		rc = get_context_order(fp, fromcon, reachable, nreach, ordering,
ddd80ee
-				       &nordered);
ddd80ee
+		rc = get_context_user(fp, fromcon, user, &reachable, &nreachable);
ddd80ee
+
ddd80ee
 		fclose(fp);
ddd80ee
 		if (rc < 0 && errno != ENOENT) {
ddd80ee
 			fprintf(stderr,
ddd80ee
@@ -454,8 +450,7 @@ int get_ordered_context_list(const char *user,
ddd80ee
 	fp = fopen(selinux_default_context_path(), "re");
ddd80ee
 	if (fp) {
ddd80ee
 		__fsetlocking(fp, FSETLOCKING_BYCALLER);
ddd80ee
-		rc = get_context_order(fp, fromcon, reachable, nreach, ordering,
ddd80ee
-				       &nordered);
ddd80ee
+		rc = get_context_user(fp, fromcon, user, &reachable, &nreachable);
ddd80ee
 		fclose(fp);
ddd80ee
 		if (rc < 0 && errno != ENOENT) {
ddd80ee
 			fprintf(stderr,
ddd80ee
@@ -463,40 +458,19 @@ int get_ordered_context_list(const char *user,
ddd80ee
 				__FUNCTION__, selinux_default_context_path());
ddd80ee
 			/* Fall through */
ddd80ee
 		}
ddd80ee
-		rc = 0;
ddd80ee
 	}
ddd80ee
 
ddd80ee
-	if (!nordered)
ddd80ee
+	if (!nreachable)
ddd80ee
 		goto failsafe;
ddd80ee
 
ddd80ee
-	/* Apply the ordering. */
ddd80ee
-	co = malloc(nreach * sizeof(struct context_order));
ddd80ee
-	if (!co)
ddd80ee
-		goto failsafe;
ddd80ee
-	for (i = 0; i < nreach; i++) {
ddd80ee
-		co[i].con = reachable[i];
ddd80ee
-		co[i].order = ordering[i];
ddd80ee
-	}
ddd80ee
-	qsort(co, nreach, sizeof(struct context_order), order_compare);
ddd80ee
-	for (i = 0; i < nreach; i++)
ddd80ee
-		reachable[i] = co[i].con;
ddd80ee
-	free(co);
ddd80ee
-
ddd80ee
-	/* Only report the ordered entries to the caller. */
ddd80ee
-	if (nordered <= nreach) {
ddd80ee
-		for (i = nordered; i < nreach; i++)
ddd80ee
-			free(reachable[i]);
ddd80ee
-		reachable[nordered] = NULL;
ddd80ee
-		rc = nordered;
ddd80ee
-	}
ddd80ee
-
ddd80ee
       out:
ddd80ee
-	if (rc > 0)
ddd80ee
+	if (nreachable > 0) {
ddd80ee
 		*list = reachable;
ddd80ee
+		rc = nreachable;
ddd80ee
+	}
ddd80ee
 	else
ddd80ee
 		freeconary(reachable);
ddd80ee
 
ddd80ee
-	free(ordering);
ddd80ee
 	if (freefrom)
ddd80ee
 		freecon(fromcon);
ddd80ee
 
ddd80ee
@@ -519,7 +493,7 @@ int get_ordered_context_list(const char *user,
ddd80ee
 		reachable = NULL;
ddd80ee
 		goto out;
ddd80ee
 	}
ddd80ee
-	rc = 1;			/* one context in the list */
ddd80ee
+	nreachable = 1;			/* one context in the list */
ddd80ee
 	goto out;
ddd80ee
 }
ddd80ee
 
ddd80ee
-- 
ddd80ee
2.25.1
ddd80ee