Restorecon: Ignore the stem when looking up all matches in file context

The stem is a list of top level directory (without regex metachar)
covered in the file context. And it constructs from finding the
second '/' in the regex_string; and aims to speed up the lookup by
skipping unnecessary regex matches. More contexts in
https://lore.kernel.org/selinux/200309231522.25749.russell@coker.com.au/

However, this caused some issue when we try to find all the partial
matches for a root directory. For example, the path "/data" doesn't
have a stem while the regex "/data/misc/(/.*)?" has "/data" as the
stem. As a result, all the regex for the subdirs of /data will not
considered as a match for "/data". And the restorecon will wrongly
skip on top level "/data" when there's a context change to one of
subdir.

This CL always includes the stem when compiling the regex in all
circumstances. Also, it ignores the stem id check in the "match all"
case, while the behavior for the single match stays unchanged. I will
collect more data to find out if stem id check is still necessary at
all with the new restorecon logic.

Bug: 62302954
Bug: 127946548
Test: run restorecon on "/data"; change the context of one subdir and
run again, and the context is restored on that subdir; search the caller
of regex_match

Change-Id: I4d6e554bb6abe124055782769d2f95083ed6c3a1
diff --git a/libselinux/src/label_file.c b/libselinux/src/label_file.c
index fe43c4a..fd3e013 100644
--- a/libselinux/src/label_file.c
+++ b/libselinux/src/label_file.c
@@ -39,18 +39,17 @@
 
 /* find the stem of a file name, returns the index into stem_arr (or -1 if
  * there is no match - IE for a file in the root directory or a regex that is
- * too complex for us).  Makes buf point to the text AFTER the stem. */
-static int find_stem_from_file(struct saved_data *data, const char **buf)
+ * too complex for us). */
+static int find_stem_from_file(struct saved_data *data, const char *key)
 {
 	int i;
-	int stem_len = get_stem_from_file_name(*buf);
+	int stem_len = get_stem_from_file_name(key);
 
 	if (!stem_len)
 		return -1;
 	for (i = 0; i < data->num_stems; i++) {
 		if (stem_len == data->stem_arr[i].len
-		    && !strncmp(*buf, data->stem_arr[i].buf, stem_len)) {
-			*buf += stem_len;
+		    && !strncmp(key, data->stem_arr[i].buf, stem_len)) {
 			return i;
 		}
 	}
@@ -906,7 +905,6 @@
 	struct spec *spec_arr = data->spec_arr;
 	int i, rc, file_stem;
 	mode_t mode = (mode_t)type;
-	const char *buf;
 	char *clean_key = NULL;
 	const char *prev_slash, *next_slash;
 	unsigned int sofar = 0;
@@ -949,8 +947,7 @@
 	if (sub)
 		key = sub;
 
-	buf = key;
-	file_stem = find_stem_from_file(data, &buf);
+	file_stem = find_stem_from_file(data, key);
 	mode &= S_IFMT;
 
 	/*
@@ -963,15 +960,15 @@
 		 * stem as the file AND if the spec in question has no mode
 		 * specified or if the mode matches the file mode then we do
 		 * a regex check        */
-		if ((spec->stem_id == -1 || spec->stem_id == file_stem) &&
+		bool stem_matches = spec->stem_id == -1 || spec->stem_id == file_stem;
+		// Don't check the stem if we want to find partial matches.
+                // Otherwise the case "/abc/efg/(/.*)?" will be considered
+                //a miss for "/abc".
+		if ((partial || stem_matches) &&
 				(!mode || !spec->mode || mode == spec->mode)) {
-			if (compile_regex(data, spec, NULL) < 0)
+			if (compile_regex(spec, NULL) < 0)
 				goto finish;
-			if (spec->stem_id == -1)
-				rc = regex_match(spec->regex, key, partial);
-			else
-				rc = regex_match(spec->regex, buf, partial);
-
+			rc = regex_match(spec->regex, key, partial);
 			if (rc == REGEX_MATCH || (partial && rc == REGEX_MATCH_PARTIAL)) {
 				if (rc == REGEX_MATCH) {
 					spec->matches++;
diff --git a/libselinux/src/label_file.h b/libselinux/src/label_file.h
index 47859ba..6f4ee10 100644
--- a/libselinux/src/label_file.h
+++ b/libselinux/src/label_file.h
@@ -336,13 +336,11 @@
 	return 0;
 }
 
-static inline int compile_regex(struct saved_data *data, struct spec *spec,
-					    const char **errbuf)
+static inline int compile_regex(struct spec *spec, const char **errbuf)
 {
 	char *reg_buf, *anchored_regex, *cp;
 	struct regex_error_data error_data;
 	static char regex_error_format_buffer[256];
-	struct stem *stem_arr = data->stem_arr;
 	size_t len;
 	int rc;
 	bool regex_compiled;
@@ -379,11 +377,7 @@
 		return 0;
 	}
 
-	/* Skip the fixed stem. */
 	reg_buf = spec->regex_str;
-	if (spec->stem_id >= 0)
-		reg_buf += stem_arr[spec->stem_id].len;
-
 	/* Anchor the regular expression. */
 	len = strlen(reg_buf);
 	cp = anchored_regex = malloc(len + 3);
@@ -501,7 +495,7 @@
 	data->nspec++;
 
 	if (rec->validating
-			&& compile_regex(data, &spec_arr[nspec], &errbuf)) {
+			&& compile_regex(&spec_arr[nspec], &errbuf)) {
 		COMPAT_LOG(SELINUX_ERROR,
 			   "%s:  line %u has invalid regex %s:  %s\n",
 			   path, lineno, regex, errbuf);