All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH testsuite v2 1/2] tests/file_contexts: refactor existing tests
@ 2026-07-23  3:13 Thiébaud Weksteen
  2026-07-23  3:13 ` [PATCH testsuite v2 2/2] tests/file_contexts: add tests for multiple SELABEL_OPT_PATH Thiébaud Weksteen
  0 siblings, 1 reply; 3+ messages in thread
From: Thiébaud Weksteen @ 2026-07-23  3:13 UTC (permalink / raw)
  To: selinux, Stephen Smalley
  Cc: James Carter, Christian Göttsche, Ondrej Mosnacek,
	Thiébaud Weksteen

Change the logging format when reporting an error to better capture the
location and context of the error.

Split tests into functions to better isolate the tested behaviour.

Signed-off-by: Thiébaud Weksteen <tweek@google.com>
---
 tests/file_contexts/internal.c       | 16 ++---
 tests/file_contexts/internal.h       | 20 +++++-
 tests/file_contexts/test_lookup.c    | 60 ++++++++++++------
 tests/file_contexts/test_open.c      | 94 +++++++++++++++++-----------
 tests/file_contexts/test_open_base.c | 46 ++++++++++----
 tests/file_contexts/test_validate.c  | 37 ++++++-----
 6 files changed, 178 insertions(+), 95 deletions(-)

diff --git a/tests/file_contexts/internal.c b/tests/file_contexts/internal.c
index 22b5790..ba45dd6 100644
--- a/tests/file_contexts/internal.c
+++ b/tests/file_contexts/internal.c
@@ -10,8 +10,8 @@
 
 #include "internal.h"
 
-void assertContextsMatch(struct selabel_handle *hnd, struct test_t *tests,
-			 size_t n)
+void assertContextsMatch(struct selabel_handle *hnd, const char *log_prefix,
+			 struct test_t *tests, size_t n)
 {
 	for (int i = 0; i < n; i++) {
 		char *context = NULL;
@@ -19,20 +19,22 @@ void assertContextsMatch(struct selabel_handle *hnd, struct test_t *tests,
 
 		if (selabel_lookup(hnd, &context, test.path, S_IFREG)) {
 			if (test.context) {
-				perror("file_contexts:selabel_lookup");
-				fprintf(stderr, "Lookup for %s failed\n", test.path);
+				log_errno("Lookup for %s from %s failed",
+					  test.path, log_prefix);
 				exit(2);
 			}
 			// Expected failure. Continue to the next test.
 			continue;
 		} else if (!test.context) {
-			fprintf(stderr, "Lookup for %s was supposed to failed\n", test.path);
+			log_err("Lookup for %s from %s was supposed to failed but got %s",
+				test.path, log_prefix, context);
 			exit(2);
 		}
 
 		if (strcmp(context, tests[i].context)) {
-			fprintf(stderr, "Lookup for %s returned %s, expected %s\n",
-				tests[i].path, context, tests[i].context);
+			log_err("Lookup for %s from %s returned %s, expected %s",
+				tests[i].path, log_prefix, context,
+				tests[i].context);
 			exit(2);
 		}
 
diff --git a/tests/file_contexts/internal.h b/tests/file_contexts/internal.h
index a07548f..c51bbc6 100644
--- a/tests/file_contexts/internal.h
+++ b/tests/file_contexts/internal.h
@@ -1,8 +1,21 @@
+#include <errno.h>
+#include <string.h>
+
 #include <selinux/label.h>
 #include <selinux/selinux.h>
 
 #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
 
+/* Log an error message with the file name, function name and line */
+#define log_err(fmt, ...)                                                    \
+	fprintf(stderr, "%s:%s:%d: " fmt "\n", __FILE__, __func__, __LINE__, \
+		##__VA_ARGS__)
+
+/* Log an error message as well as errno */
+#define log_errno(fmt, ...)                                            \
+	fprintf(stderr, "%s:%s:%d " fmt " (%s)\n", __FILE__, __func__, \
+		__LINE__, ##__VA_ARGS__, strerror(errno))
+
 /* Structure to capture a test. The |path| will be resolved and expected to
  * match the |context|. If |context| is NULL, the lookup is expected to fail. */
 struct test_t {
@@ -11,6 +24,7 @@ struct test_t {
 };
 
 /* Assert that all paths described in |tests| resolve appropriately. |hnd| must
- * be opened. |n| is the number of tests in |tests|. */
-void assertContextsMatch(struct selabel_handle *hnd, struct test_t *tests,
-			 size_t n);
+ * be opened. |n| is the number of tests in |tests|. |log_prefix| is added to
+ * any log message */
+void assertContextsMatch(struct selabel_handle *hnd, const char *log_prefix,
+			 struct test_t *tests, size_t n);
diff --git a/tests/file_contexts/test_lookup.c b/tests/file_contexts/test_lookup.c
index 9a54d1d..5957db4 100644
--- a/tests/file_contexts/test_lookup.c
+++ b/tests/file_contexts/test_lookup.c
@@ -9,38 +9,58 @@
 
 #include "internal.h"
 
-int main(int argc, char **argv)
+void test_lookup(const char *basedir)
 {
-	struct selabel_handle *hnd;
-
-	if (argc != 2) {
-		fprintf(stderr, "basedir not provided\n");
-		exit(1);
-	}
-
 	char *path;
-	asprintf(&path, "%s/f2.fc", argv[1]);
-	struct selinux_opt f2_opts[] = {
-		{ .type = SELABEL_OPT_PATH, .value = path }
+	asprintf(&path, "%s/f2.fc", basedir);
+	struct selinux_opt f2_opts[] = { {
+			.type = SELABEL_OPT_PATH,
+			.value = path
+		}
 	};
 
-	hnd = selabel_open(SELABEL_CTX_FILE, f2_opts, ARRAY_SIZE(f2_opts));
-
+	struct selabel_handle *hnd =
+		selabel_open(SELABEL_CTX_FILE, f2_opts, ARRAY_SIZE(f2_opts));
 	free(path);
 
 	if (!hnd) {
-		perror("file_context:f2_options");
+		log_errno("Unable to open file backend");
 		exit(2);
 	}
 
 	struct test_t tests[] = {
-		{ .path = "/base", .context = "system_u:object_r:test_base_t:s0" },
-		{ .path = "/base/unkown", .context = "system_u:object_r:test_base_wildcard_t:s0" },
-		{ .path = "/base/sub", .context = "system_u:object_r:test_base_sub_t:s0" },
-		{ .path = "/base/file.list", .context = "system_u:object_r:test_file_list_t:s0" },
-		{ .path = "/base/file_list", .context = "system_u:object_r:test_base_wildcard_t:s0" },
+		{
+			.path = "/base",
+			.context = "system_u:object_r:test_base_t:s0"
+		},
+		{
+			.path = "/base/unkown",
+			.context = "system_u:object_r:test_base_wildcard_t:s0"
+		},
+		{
+			.path = "/base/sub",
+			.context = "system_u:object_r:test_base_sub_t:s0"
+		},
+		{
+			.path = "/base/file.list",
+			.context = "system_u:object_r:test_file_list_t:s0"
+		},
+		{
+			.path = "/base/file_list",
+			.context = "system_u:object_r:test_base_wildcard_t:s0"
+		},
 	};
-	assertContextsMatch(hnd, tests, ARRAY_SIZE(tests));
+	assertContextsMatch(hnd, __func__, tests, ARRAY_SIZE(tests));
+}
+
+int main(int argc, char **argv)
+{
+	if (argc != 2) {
+		log_err("basedir not provided");
+		exit(1);
+	}
+
+	test_lookup(argv[1]);
 
 	return 0;
 }
diff --git a/tests/file_contexts/test_open.c b/tests/file_contexts/test_open.c
index eae5691..4dd3300 100644
--- a/tests/file_contexts/test_open.c
+++ b/tests/file_contexts/test_open.c
@@ -1,6 +1,5 @@
 #include <stdio.h>
 #include <stdlib.h>
-#include <string.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
@@ -8,75 +7,96 @@
 #include <selinux/label.h>
 #include <selinux/selinux.h>
 
-int main(int argc, char **argv)
+#include "internal.h"
+
+void test_no_options(void)
 {
-	bool has_default_path = false;
-	struct selabel_handle *hnd;
 	struct stat default_stat;
+	if (stat(selinux_file_context_path(), &default_stat))
+		return;
 
-	if (argc != 2) {
-		fprintf(stderr, "basedir not provided\n");
-		exit(1);
-	}
+	/* Empty options */
+	struct selabel_handle *hnd = selabel_open(
+					     SELABEL_CTX_FILE, /* options= */ NULL, /* nopt= */ 0);
 
-	const char *default_path = selinux_file_context_path();
-	if (!stat(default_path, &default_stat)) {
-		has_default_path = true;
+	if (!hnd) {
+		log_errno("Unable to open default content file");
+		exit(2);
 	}
+	selabel_close(hnd);
+}
 
-	if (has_default_path) {
-		/* Empty options */
-		hnd = selabel_open(SELABEL_CTX_FILE, /* options= */ NULL, /* nopt= */ 0);
+void test_null_path(void)
+{
+	struct stat default_stat;
+	if (stat(selinux_file_context_path(), &default_stat))
+		return;
 
-		if (!hnd) {
-			perror("file_context:no_options");
-			exit(2);
+	/* Default options */
+	struct selinux_opt null_opts[] = { {
+			.type = SELABEL_OPT_PATH,
+			.value = NULL
 		}
-		selabel_close(hnd);
-
-		/* Default options */
-		struct selinux_opt null_opts[] = {
-			{ .type = SELABEL_OPT_PATH, .value = NULL }
-		};
+	};
 
-		hnd = selabel_open(SELABEL_CTX_FILE, /* options= */ null_opts, /* nopt= */ 1);
+	struct selabel_handle *hnd = selabel_open(SELABEL_CTX_FILE, null_opts,
+						  ARRAY_SIZE(null_opts));
 
-		if (!hnd) {
-			perror("file_context:default_options");
-			exit(2);
-		}
-		selabel_close(hnd);
+	if (!hnd) {
+		log_errno("Unable to open default content file");
+		exit(2);
 	}
+	selabel_close(hnd);
+}
 
+void test_valid_path(const char *basedir)
+{
 	/* f1.fc file */
 	char *path;
-	asprintf(&path, "%s/f1.fc", argv[1]);
-	struct selinux_opt f1_opts[] = {
-		{ .type = SELABEL_OPT_PATH, .value = path }
+	asprintf(&path, "%s/f1.fc", basedir);
+	struct selinux_opt f1_opts[] = { {
+			.type = SELABEL_OPT_PATH,
+			.value = path
+		}
 	};
 
-	hnd = selabel_open(SELABEL_CTX_FILE, /* options= */ f1_opts, /* nopt= */ 1);
+	struct selabel_handle *hnd =
+		selabel_open(SELABEL_CTX_FILE, f1_opts, ARRAY_SIZE(f1_opts));
 	free(path);
 
 	if (!hnd) {
-		perror("file_context:f1_options");
+		log_errno("Unable to open file backend");
 		exit(2);
 	}
 
 	char *context = NULL;
 	if (selabel_lookup(hnd, &context, "/", S_IFREG)) {
-		perror("file_contexts:f1_lookup");
+		log_errno("Unable to lookup \"/\"");
 		exit(2);
 	}
 
-	if (strcmp(context, "system_u:object_r:rootfs:s0")) {
-		perror("file_contexts:f1_strcmp");
+	const char *expected = "system_u:object_r:rootfs:s0";
+	if (strcmp(context, expected)) {
+		log_err("Incorrect context returned, expected %s got %s",
+			expected, context);
 		exit(2);
 	}
 
 	free(context);
 
 	selabel_close(hnd);
+}
+
+int main(int argc, char **argv)
+{
+	if (argc != 2) {
+		log_err("basedir not provided");
+		exit(1);
+	}
+
+	test_no_options();
+	test_null_path();
+	test_valid_path(argv[1]);
 
 	return 0;
 }
diff --git a/tests/file_contexts/test_open_base.c b/tests/file_contexts/test_open_base.c
index 33b0cac..3e4b4c6 100644
--- a/tests/file_contexts/test_open_base.c
+++ b/tests/file_contexts/test_open_base.c
@@ -22,26 +22,40 @@ void test_default_options(const char *basedir)
 
 	char *path;
 	asprintf(&path, "%s/f3.fc", basedir);
-	struct selinux_opt f3_opts[] = {
-		{ .type = SELABEL_OPT_PATH, .value = path }
+	struct selinux_opt f3_opts[] = { {
+			.type = SELABEL_OPT_PATH,
+			.value = path
+		}
 	};
 
 	hnd = selabel_open(SELABEL_CTX_FILE, f3_opts, ARRAY_SIZE(f3_opts));
 	free(path);
 
 	if (!hnd) {
-		perror("file_context:f3_default_options");
+		log_errno("Unable to open file backend");
 		exit(2);
 	}
 
 	struct test_t tests[] = {
 		{ .path = "/", .context = "system_u:object_r:rootfs:s0" },
-		{ .path = "/local", .context = "system_u:object_r:test_local:s0" },
-		{ .path = "/homedirs", .context = "system_u:object_r:test_homedirs:s0" },
-		{ .path = "/sub", .context = "system_u:object_r:test_subbed:s0" },
-		{ .path = "/sub_dist", .context = "system_u:object_r:test_subbed:s0" },
+		{
+			.path = "/local",
+			.context = "system_u:object_r:test_local:s0"
+		},
+		{
+			.path = "/homedirs",
+			.context = "system_u:object_r:test_homedirs:s0"
+		},
+		{
+			.path = "/sub",
+			.context = "system_u:object_r:test_subbed:s0"
+		},
+		{
+			.path = "/sub_dist",
+			.context = "system_u:object_r:test_subbed:s0"
+		},
 	};
-	assertContextsMatch(hnd, tests, ARRAY_SIZE(tests));
+	assertContextsMatch(hnd, __func__, tests, ARRAY_SIZE(tests));
 
 	selabel_close(hnd);
 }
@@ -62,7 +76,7 @@ void test_base_only_option(const char *basedir)
 	free(path);
 
 	if (!hnd) {
-		perror("file_context:f3_base_only_options");
+		log_errno("Unable to open file backend");
 		exit(2);
 	}
 
@@ -70,10 +84,16 @@ void test_base_only_option(const char *basedir)
 		{ .path = "/", .context = "system_u:object_r:rootfs:s0" },
 		{ .path = "/local", .context = NULL },
 		{ .path = "/homedirs", .context = NULL },
-		{ .path = "/sub", .context = "system_u:object_r:test_subbed:s0" },
-		{ .path = "/sub_dist", .context = "system_u:object_r:test_subbed:s0" },
+		{
+			.path = "/sub",
+			.context = "system_u:object_r:test_subbed:s0"
+		},
+		{
+			.path = "/sub_dist",
+			.context = "system_u:object_r:test_subbed:s0"
+		},
 	};
-	assertContextsMatch(hnd, tests, ARRAY_SIZE(tests));
+	assertContextsMatch(hnd, __func__, tests, ARRAY_SIZE(tests));
 
 	selabel_close(hnd);
 }
@@ -81,7 +101,7 @@ void test_base_only_option(const char *basedir)
 int main(int argc, char **argv)
 {
 	if (argc != 2) {
-		fprintf(stderr, "basedir not provided\n");
+		log_err("basedir not provided");
 		exit(1);
 	}
 
diff --git a/tests/file_contexts/test_validate.c b/tests/file_contexts/test_validate.c
index 8abefed..aa71c22 100644
--- a/tests/file_contexts/test_validate.c
+++ b/tests/file_contexts/test_validate.c
@@ -1,6 +1,5 @@
 #include <stdio.h>
 #include <stdlib.h>
-#include <string.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
@@ -10,47 +9,55 @@
 
 #include "internal.h"
 
-int main(int argc, char **argv)
+void test_validate_unknown_fails(const char *basedir)
 {
 	char *path;
-	struct selabel_handle *hnd;
-
-	if (argc != 2) {
-		fprintf(stderr, "basedir not provided\n");
-		exit(1);
-	}
-
-	asprintf(&path, "%s/f1.fc", argv[1]);
+	asprintf(&path, "%s/f1.fc", basedir);
 	struct selinux_opt f1_opts[] = {
 		{ .type = SELABEL_OPT_PATH, .value = path },
 		{ .type = SELABEL_OPT_VALIDATE, .value = "1" }
 	};
 
 	/* f1 types are not defined in the test policy. */
-	hnd = selabel_open(SELABEL_CTX_FILE, f1_opts, ARRAY_SIZE(f1_opts));
+	struct selabel_handle *hnd =
+		selabel_open(SELABEL_CTX_FILE, f1_opts, ARRAY_SIZE(f1_opts));
 	free(path);
 
 	if (hnd) {
-		fprintf(stderr, "The validation of f1 should have failed.\n");
+		log_err("The validation of f1 should have failed");
+		selabel_close(hnd);
 		exit(2);
 	}
+}
 
-	asprintf(&path, "%s/f2.fc", argv[1]);
+void test_validate_known_succeeds(const char *basedir)
+{
+	char *path;
+	asprintf(&path, "%s/f2.fc", basedir);
 	struct selinux_opt f2_opts[] = {
 		{ .type = SELABEL_OPT_PATH, .value = path },
 		{ .type = SELABEL_OPT_VALIDATE, .value = "1" }
 	};
 
 	/* f2 types are defined in the test policy. */
-	hnd = selabel_open(SELABEL_CTX_FILE, f2_opts, ARRAY_SIZE(f2_opts));
+	struct selabel_handle *hnd =
+		selabel_open(SELABEL_CTX_FILE, f2_opts, ARRAY_SIZE(f2_opts));
 	free(path);
 
 	if (!hnd) {
-		perror("Unable to read valid file_contexts");
+		log_errno("Unable to read valid file_contexts");
 		exit(2);
 	}
 
 	selabel_close(hnd);
+}
+
+int main(int argc, char **argv)
+{
+	if (argc != 2) {
+		log_err("basedir not provided");
+		exit(1);
+	}
 
 	return 0;
 }
-- 
2.55.0.229.g6434b31f56-goog


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* [PATCH testsuite v2 2/2] tests/file_contexts: add tests for multiple SELABEL_OPT_PATH
  2026-07-23  3:13 [PATCH testsuite v2 1/2] tests/file_contexts: refactor existing tests Thiébaud Weksteen
@ 2026-07-23  3:13 ` Thiébaud Weksteen
  2026-07-23 16:24   ` Stephen Smalley
  0 siblings, 1 reply; 3+ messages in thread
From: Thiébaud Weksteen @ 2026-07-23  3:13 UTC (permalink / raw)
  To: selinux, Stephen Smalley
  Cc: James Carter, Christian Göttsche, Ondrej Mosnacek,
	Thiébaud Weksteen

Add unit tests in test_multiple.c to exercise opening the file contexts
backend with multiple SELABEL_OPT_PATH options under various validation
and path configuration scenarios.

The following test functions were added:
- Verifies opening multiple file contexts without validation.
- Verifies validation failure when contexts contain undefined types.
- Verifies extra files (.subs, .local, .homedirs) are processed only for
  the primary path.
- Verifies multiple files with the same definition, no error is raised.
- Verifies context lookups when providing three distinct
  SELABEL_OPT_PATH options.

Signed-off-by: Thiébaud Weksteen <tweek@google.com>
---
 tests/file_contexts/Makefile        |   2 +-
 tests/file_contexts/test            |   5 +-
 tests/file_contexts/test_multiple.c | 233 ++++++++++++++++++++++++++++
 3 files changed, 238 insertions(+), 2 deletions(-)
 create mode 100644 tests/file_contexts/test_multiple.c

diff --git a/tests/file_contexts/Makefile b/tests/file_contexts/Makefile
index 592a65f..083ef25 100644
--- a/tests/file_contexts/Makefile
+++ b/tests/file_contexts/Makefile
@@ -1,4 +1,4 @@
-TARGETS=test_open test_lookup test_open_base test_validate
+TARGETS=test_open test_lookup test_open_base test_validate test_multiple
 CFLAGS += -O2 -Werror -Wall
 LDLIBS += -lselinux
 
diff --git a/tests/file_contexts/test b/tests/file_contexts/test
index 1534925..cd7849b 100755
--- a/tests/file_contexts/test
+++ b/tests/file_contexts/test
@@ -5,7 +5,7 @@
 
 use Test;
 
-BEGIN { plan tests => 4; }
+BEGIN { plan tests => 5; }
 
 $basedir = $0;
 $basedir =~ s|(.*)/[^/]*|$1|;
@@ -22,4 +22,7 @@ ok( $result, 0 );
 $result = system "$basedir/test_validate $basedir 2>&1";
 ok( $result, 0 );
 
+$result = system "$basedir/test_multiple $basedir 2>&1";
+ok( $result, 0 );
+
 exit;
diff --git a/tests/file_contexts/test_multiple.c b/tests/file_contexts/test_multiple.c
new file mode 100644
index 0000000..65fb726
--- /dev/null
+++ b/tests/file_contexts/test_multiple.c
@@ -0,0 +1,233 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <selinux/label.h>
+#include <selinux/selinux.h>
+
+#include "internal.h"
+
+void test_multiple_no_validation(const char *basedir)
+{
+	struct selabel_handle *hnd;
+
+	/* f1.fc and f2.fc file */
+	char *f1_path, *f2_path;
+	asprintf(&f1_path, "%s/f1.fc", basedir);
+	asprintf(&f2_path, "%s/f2.fc", basedir);
+	struct selinux_opt opts[] = {
+		{ .type = SELABEL_OPT_PATH, .value = f1_path },
+		{ .type = SELABEL_OPT_PATH, .value = f2_path }
+	};
+
+	hnd = selabel_open(SELABEL_CTX_FILE, opts, ARRAY_SIZE(opts));
+	free(f1_path);
+	free(f2_path);
+
+	if (!hnd) {
+		log_errno("Unable to open file backend");
+		exit(2);
+	}
+
+	struct test_t tests[] = {
+		{ .path = "/", .context = "system_u:object_r:rootfs:s0" },
+		{
+			.path = "/base",
+			.context = "system_u:object_r:test_base_t:s0"
+		},
+	};
+	assertContextsMatch(hnd, __func__, tests, ARRAY_SIZE(tests));
+
+	selabel_close(hnd);
+}
+
+void test_multiple_with_validation(const char *basedir)
+{
+	struct selabel_handle *hnd;
+
+	/* f1.fc and f2.fc file - f1 has undefined type rootfs in test policy */
+	char *f1_path, *f2_path;
+	asprintf(&f1_path, "%s/f1.fc", basedir);
+	asprintf(&f2_path, "%s/f2.fc", basedir);
+	struct selinux_opt opts[] = {
+		{ .type = SELABEL_OPT_PATH, .value = f1_path },
+		{ .type = SELABEL_OPT_PATH, .value = f2_path },
+		{ .type = SELABEL_OPT_VALIDATE, .value = "1" }
+	};
+
+	hnd = selabel_open(SELABEL_CTX_FILE, opts, ARRAY_SIZE(opts));
+	free(f1_path);
+	free(f2_path);
+
+	if (hnd) {
+		log_err("Validation of f1 and f2 should have failed");
+		selabel_close(hnd);
+		exit(2);
+	}
+}
+
+void test_multiple_with_extras(const char *basedir)
+{
+	struct selabel_handle *hnd;
+	char *f2_path, *f3_path;
+	asprintf(&f2_path, "%s/f2.fc", basedir);
+	asprintf(&f3_path, "%s/f3.fc", basedir);
+
+	/* 1. f3.fc is the first path: extras (.subs, .local, .homedirs) of f3 ARE processed */
+	struct selinux_opt opts_f3_first[] = {
+		{ .type = SELABEL_OPT_PATH, .value = f3_path },
+		{ .type = SELABEL_OPT_PATH, .value = f2_path }
+	};
+	hnd = selabel_open(SELABEL_CTX_FILE, opts_f3_first,
+			   ARRAY_SIZE(opts_f3_first));
+	if (!hnd) {
+		log_errno("Unable to open file backend");
+		exit(2);
+	}
+
+	struct test_t tests_f3_first[] = {
+		{ .path = "/", .context = "system_u:object_r:rootfs:s0" },
+		{
+			.path = "/sub",
+			.context = "system_u:object_r:test_subbed:s0"
+		},
+		{
+			.path = "/local",
+			.context = "system_u:object_r:test_local:s0"
+		},
+		{
+			.path = "/homedirs",
+			.context = "system_u:object_r:test_homedirs:s0"
+		},
+		{
+			.path = "/base",
+			.context = "system_u:object_r:test_base_t:s0"
+		},
+	};
+	assertContextsMatch(hnd, __func__, tests_f3_first,
+			    ARRAY_SIZE(tests_f3_first));
+	selabel_close(hnd);
+
+	/* 2. f2.fc is the first path, f3.fc is second: extras from f3 are NOT processed */
+	struct selinux_opt opts_f2_first[] = {
+		{ .type = SELABEL_OPT_PATH, .value = f2_path },
+		{ .type = SELABEL_OPT_PATH, .value = f3_path }
+	};
+	hnd = selabel_open(SELABEL_CTX_FILE, opts_f2_first,
+			   ARRAY_SIZE(opts_f2_first));
+	if (!hnd) {
+		log_errno("Unable to open file backend");
+		exit(2);
+	}
+
+	struct test_t tests_f2_first[] = {
+		{
+			.path = "/base",
+			.context = "system_u:object_r:test_base_t:s0"
+		},
+		{ .path = "/", .context = "system_u:object_r:rootfs:s0" },
+		{
+			.path = "/subbed",
+			.context = "system_u:object_r:test_subbed:s0"
+		},
+		/* /sub, /local, /homedirs should NOT match the f3 extra contexts */
+		{ .path = "/sub", .context = NULL },
+		{ .path = "/local", .context = NULL },
+		{ .path = "/homedirs", .context = NULL },
+	};
+	assertContextsMatch(hnd, __func__, tests_f2_first,
+			    ARRAY_SIZE(tests_f2_first));
+	selabel_close(hnd);
+
+	free(f2_path);
+	free(f3_path);
+}
+
+void test_multiple_three_paths(const char *basedir)
+{
+	struct selabel_handle *hnd;
+	char *f1_path, *f2_path, *f3_path;
+	asprintf(&f1_path, "%s/f1.fc", basedir);
+	asprintf(&f2_path, "%s/f2.fc", basedir);
+	asprintf(&f3_path, "%s/f3.fc", basedir);
+
+	struct selinux_opt opts[] = {
+		{ .type = SELABEL_OPT_PATH, .value = f1_path },
+		{ .type = SELABEL_OPT_PATH, .value = f2_path },
+		{ .type = SELABEL_OPT_PATH, .value = f3_path }
+	};
+
+	hnd = selabel_open(SELABEL_CTX_FILE, opts, ARRAY_SIZE(opts));
+	free(f1_path);
+	free(f2_path);
+	free(f3_path);
+
+	if (!hnd) {
+		log_errno("Unable to open file backend");
+		exit(2);
+	}
+
+	struct test_t tests[] = {
+		{ .path = "/", .context = "system_u:object_r:rootfs:s0" },
+		{
+			.path = "/base",
+			.context = "system_u:object_r:test_base_t:s0"
+		},
+		{
+			.path = "/subbed",
+			.context = "system_u:object_r:test_subbed:s0"
+		},
+	};
+	assertContextsMatch(hnd, __func__, tests, ARRAY_SIZE(tests));
+	selabel_close(hnd);
+}
+
+void test_multiple_duplicate_validation(const char *basedir)
+{
+	struct selabel_handle *hnd;
+
+	/* Two copies of f2.fc to provide duplicate specifications */
+	char *f2_path;
+	asprintf(&f2_path, "%s/f2.fc", basedir);
+	struct selinux_opt opts[] = {
+		{ .type = SELABEL_OPT_PATH, .value = f2_path },
+		{ .type = SELABEL_OPT_PATH, .value = f2_path },
+		{ .type = SELABEL_OPT_VALIDATE, .value = "1" }
+	};
+
+	hnd = selabel_open(SELABEL_CTX_FILE, opts, ARRAY_SIZE(opts));
+	free(f2_path);
+
+	if (!hnd) {
+		log_errno("Unable to open file backend");
+		exit(2);
+	}
+
+	struct test_t tests[] = {
+		{
+			.path = "/base",
+			.context = "system_u:object_r:test_base_t:s0"
+		},
+	};
+	assertContextsMatch(hnd, __func__, tests, ARRAY_SIZE(tests));
+
+	selabel_close(hnd);
+}
+
+int main(int argc, char **argv)
+{
+	if (argc != 2) {
+		log_err("basedir not provided");
+		exit(1);
+	}
+
+	test_multiple_no_validation(argv[1]);
+	test_multiple_with_validation(argv[1]);
+	test_multiple_duplicate_validation(argv[1]);
+	test_multiple_with_extras(argv[1]);
+	test_multiple_three_paths(argv[1]);
+
+	return 0;
+}
-- 
2.55.0.229.g6434b31f56-goog


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [PATCH testsuite v2 2/2] tests/file_contexts: add tests for multiple SELABEL_OPT_PATH
  2026-07-23  3:13 ` [PATCH testsuite v2 2/2] tests/file_contexts: add tests for multiple SELABEL_OPT_PATH Thiébaud Weksteen
@ 2026-07-23 16:24   ` Stephen Smalley
  0 siblings, 0 replies; 3+ messages in thread
From: Stephen Smalley @ 2026-07-23 16:24 UTC (permalink / raw)
  To: Thiébaud Weksteen
  Cc: selinux, James Carter, Christian Göttsche, Ondrej Mosnacek

On Wed, Jul 22, 2026 at 11:13 PM Thiébaud Weksteen <tweek@google.com> wrote:
>
> Add unit tests in test_multiple.c to exercise opening the file contexts
> backend with multiple SELABEL_OPT_PATH options under various validation
> and path configuration scenarios.
>
> The following test functions were added:
> - Verifies opening multiple file contexts without validation.
> - Verifies validation failure when contexts contain undefined types.
> - Verifies extra files (.subs, .local, .homedirs) are processed only for
>   the primary path.
> - Verifies multiple files with the same definition, no error is raised.
> - Verifies context lookups when providing three distinct
>   SELABEL_OPT_PATH options.
>
> Signed-off-by: Thiébaud Weksteen <tweek@google.com>

We need to avoid breaking the testsuite when it is run with older
libselinux before
your patch too. Should just skip the multi-file tests when the system libselinux
doesn't support it.

> ---
>  tests/file_contexts/Makefile        |   2 +-
>  tests/file_contexts/test            |   5 +-
>  tests/file_contexts/test_multiple.c | 233 ++++++++++++++++++++++++++++
>  3 files changed, 238 insertions(+), 2 deletions(-)
>  create mode 100644 tests/file_contexts/test_multiple.c
>
> diff --git a/tests/file_contexts/Makefile b/tests/file_contexts/Makefile
> index 592a65f..083ef25 100644
> --- a/tests/file_contexts/Makefile
> +++ b/tests/file_contexts/Makefile
> @@ -1,4 +1,4 @@
> -TARGETS=test_open test_lookup test_open_base test_validate
> +TARGETS=test_open test_lookup test_open_base test_validate test_multiple
>  CFLAGS += -O2 -Werror -Wall
>  LDLIBS += -lselinux
>
> diff --git a/tests/file_contexts/test b/tests/file_contexts/test
> index 1534925..cd7849b 100755
> --- a/tests/file_contexts/test
> +++ b/tests/file_contexts/test
> @@ -5,7 +5,7 @@
>
>  use Test;
>
> -BEGIN { plan tests => 4; }
> +BEGIN { plan tests => 5; }
>
>  $basedir = $0;
>  $basedir =~ s|(.*)/[^/]*|$1|;
> @@ -22,4 +22,7 @@ ok( $result, 0 );
>  $result = system "$basedir/test_validate $basedir 2>&1";
>  ok( $result, 0 );
>
> +$result = system "$basedir/test_multiple $basedir 2>&1";
> +ok( $result, 0 );
> +
>  exit;
> diff --git a/tests/file_contexts/test_multiple.c b/tests/file_contexts/test_multiple.c
> new file mode 100644
> index 0000000..65fb726
> --- /dev/null
> +++ b/tests/file_contexts/test_multiple.c
> @@ -0,0 +1,233 @@
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <sys/stat.h>
> +#include <sys/types.h>
> +#include <unistd.h>
> +
> +#include <selinux/label.h>
> +#include <selinux/selinux.h>
> +
> +#include "internal.h"
> +
> +void test_multiple_no_validation(const char *basedir)
> +{
> +       struct selabel_handle *hnd;
> +
> +       /* f1.fc and f2.fc file */
> +       char *f1_path, *f2_path;
> +       asprintf(&f1_path, "%s/f1.fc", basedir);
> +       asprintf(&f2_path, "%s/f2.fc", basedir);
> +       struct selinux_opt opts[] = {
> +               { .type = SELABEL_OPT_PATH, .value = f1_path },
> +               { .type = SELABEL_OPT_PATH, .value = f2_path }
> +       };
> +
> +       hnd = selabel_open(SELABEL_CTX_FILE, opts, ARRAY_SIZE(opts));
> +       free(f1_path);
> +       free(f2_path);
> +
> +       if (!hnd) {
> +               log_errno("Unable to open file backend");
> +               exit(2);
> +       }
> +
> +       struct test_t tests[] = {
> +               { .path = "/", .context = "system_u:object_r:rootfs:s0" },
> +               {
> +                       .path = "/base",
> +                       .context = "system_u:object_r:test_base_t:s0"
> +               },
> +       };
> +       assertContextsMatch(hnd, __func__, tests, ARRAY_SIZE(tests));
> +
> +       selabel_close(hnd);
> +}
> +
> +void test_multiple_with_validation(const char *basedir)
> +{
> +       struct selabel_handle *hnd;
> +
> +       /* f1.fc and f2.fc file - f1 has undefined type rootfs in test policy */
> +       char *f1_path, *f2_path;
> +       asprintf(&f1_path, "%s/f1.fc", basedir);
> +       asprintf(&f2_path, "%s/f2.fc", basedir);
> +       struct selinux_opt opts[] = {
> +               { .type = SELABEL_OPT_PATH, .value = f1_path },
> +               { .type = SELABEL_OPT_PATH, .value = f2_path },
> +               { .type = SELABEL_OPT_VALIDATE, .value = "1" }
> +       };
> +
> +       hnd = selabel_open(SELABEL_CTX_FILE, opts, ARRAY_SIZE(opts));
> +       free(f1_path);
> +       free(f2_path);
> +
> +       if (hnd) {
> +               log_err("Validation of f1 and f2 should have failed");
> +               selabel_close(hnd);
> +               exit(2);
> +       }
> +}
> +
> +void test_multiple_with_extras(const char *basedir)
> +{
> +       struct selabel_handle *hnd;
> +       char *f2_path, *f3_path;
> +       asprintf(&f2_path, "%s/f2.fc", basedir);
> +       asprintf(&f3_path, "%s/f3.fc", basedir);
> +
> +       /* 1. f3.fc is the first path: extras (.subs, .local, .homedirs) of f3 ARE processed */
> +       struct selinux_opt opts_f3_first[] = {
> +               { .type = SELABEL_OPT_PATH, .value = f3_path },
> +               { .type = SELABEL_OPT_PATH, .value = f2_path }
> +       };
> +       hnd = selabel_open(SELABEL_CTX_FILE, opts_f3_first,
> +                          ARRAY_SIZE(opts_f3_first));
> +       if (!hnd) {
> +               log_errno("Unable to open file backend");
> +               exit(2);
> +       }
> +
> +       struct test_t tests_f3_first[] = {
> +               { .path = "/", .context = "system_u:object_r:rootfs:s0" },
> +               {
> +                       .path = "/sub",
> +                       .context = "system_u:object_r:test_subbed:s0"
> +               },
> +               {
> +                       .path = "/local",
> +                       .context = "system_u:object_r:test_local:s0"
> +               },
> +               {
> +                       .path = "/homedirs",
> +                       .context = "system_u:object_r:test_homedirs:s0"
> +               },
> +               {
> +                       .path = "/base",
> +                       .context = "system_u:object_r:test_base_t:s0"
> +               },
> +       };
> +       assertContextsMatch(hnd, __func__, tests_f3_first,
> +                           ARRAY_SIZE(tests_f3_first));
> +       selabel_close(hnd);
> +
> +       /* 2. f2.fc is the first path, f3.fc is second: extras from f3 are NOT processed */
> +       struct selinux_opt opts_f2_first[] = {
> +               { .type = SELABEL_OPT_PATH, .value = f2_path },
> +               { .type = SELABEL_OPT_PATH, .value = f3_path }
> +       };
> +       hnd = selabel_open(SELABEL_CTX_FILE, opts_f2_first,
> +                          ARRAY_SIZE(opts_f2_first));
> +       if (!hnd) {
> +               log_errno("Unable to open file backend");
> +               exit(2);
> +       }
> +
> +       struct test_t tests_f2_first[] = {
> +               {
> +                       .path = "/base",
> +                       .context = "system_u:object_r:test_base_t:s0"
> +               },
> +               { .path = "/", .context = "system_u:object_r:rootfs:s0" },
> +               {
> +                       .path = "/subbed",
> +                       .context = "system_u:object_r:test_subbed:s0"
> +               },
> +               /* /sub, /local, /homedirs should NOT match the f3 extra contexts */
> +               { .path = "/sub", .context = NULL },
> +               { .path = "/local", .context = NULL },
> +               { .path = "/homedirs", .context = NULL },
> +       };
> +       assertContextsMatch(hnd, __func__, tests_f2_first,
> +                           ARRAY_SIZE(tests_f2_first));
> +       selabel_close(hnd);
> +
> +       free(f2_path);
> +       free(f3_path);
> +}
> +
> +void test_multiple_three_paths(const char *basedir)
> +{
> +       struct selabel_handle *hnd;
> +       char *f1_path, *f2_path, *f3_path;
> +       asprintf(&f1_path, "%s/f1.fc", basedir);
> +       asprintf(&f2_path, "%s/f2.fc", basedir);
> +       asprintf(&f3_path, "%s/f3.fc", basedir);
> +
> +       struct selinux_opt opts[] = {
> +               { .type = SELABEL_OPT_PATH, .value = f1_path },
> +               { .type = SELABEL_OPT_PATH, .value = f2_path },
> +               { .type = SELABEL_OPT_PATH, .value = f3_path }
> +       };
> +
> +       hnd = selabel_open(SELABEL_CTX_FILE, opts, ARRAY_SIZE(opts));
> +       free(f1_path);
> +       free(f2_path);
> +       free(f3_path);
> +
> +       if (!hnd) {
> +               log_errno("Unable to open file backend");
> +               exit(2);
> +       }
> +
> +       struct test_t tests[] = {
> +               { .path = "/", .context = "system_u:object_r:rootfs:s0" },
> +               {
> +                       .path = "/base",
> +                       .context = "system_u:object_r:test_base_t:s0"
> +               },
> +               {
> +                       .path = "/subbed",
> +                       .context = "system_u:object_r:test_subbed:s0"
> +               },
> +       };
> +       assertContextsMatch(hnd, __func__, tests, ARRAY_SIZE(tests));
> +       selabel_close(hnd);
> +}
> +
> +void test_multiple_duplicate_validation(const char *basedir)
> +{
> +       struct selabel_handle *hnd;
> +
> +       /* Two copies of f2.fc to provide duplicate specifications */
> +       char *f2_path;
> +       asprintf(&f2_path, "%s/f2.fc", basedir);
> +       struct selinux_opt opts[] = {
> +               { .type = SELABEL_OPT_PATH, .value = f2_path },
> +               { .type = SELABEL_OPT_PATH, .value = f2_path },
> +               { .type = SELABEL_OPT_VALIDATE, .value = "1" }
> +       };
> +
> +       hnd = selabel_open(SELABEL_CTX_FILE, opts, ARRAY_SIZE(opts));
> +       free(f2_path);
> +
> +       if (!hnd) {
> +               log_errno("Unable to open file backend");
> +               exit(2);
> +       }
> +
> +       struct test_t tests[] = {
> +               {
> +                       .path = "/base",
> +                       .context = "system_u:object_r:test_base_t:s0"
> +               },
> +       };
> +       assertContextsMatch(hnd, __func__, tests, ARRAY_SIZE(tests));
> +
> +       selabel_close(hnd);
> +}
> +
> +int main(int argc, char **argv)
> +{
> +       if (argc != 2) {
> +               log_err("basedir not provided");
> +               exit(1);
> +       }
> +
> +       test_multiple_no_validation(argv[1]);
> +       test_multiple_with_validation(argv[1]);
> +       test_multiple_duplicate_validation(argv[1]);
> +       test_multiple_with_extras(argv[1]);
> +       test_multiple_three_paths(argv[1]);
> +
> +       return 0;
> +}
> --
> 2.55.0.229.g6434b31f56-goog
>

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-07-23 16:24 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-23  3:13 [PATCH testsuite v2 1/2] tests/file_contexts: refactor existing tests Thiébaud Weksteen
2026-07-23  3:13 ` [PATCH testsuite v2 2/2] tests/file_contexts: add tests for multiple SELABEL_OPT_PATH Thiébaud Weksteen
2026-07-23 16:24   ` Stephen Smalley

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.