Re: [PATCH testsuite v3 2/2] tests/file_contexts: add tests for multiple SELABEL_OPT_PATH
Stephen Smalley <[email protected]> Tue, 28 Jul 2026 13:48:11 -0400
| Newsgroups | org.kernel.vger.selinux |
|---|---|
| Message-ID | <CAEjxPJ52-2rP4JfvY3VU3QBnqnLwqX-fGepZx9ECCOoNHu_F4w@mail.gmail.com> |
On Mon, Jul 27, 2026 at 9:31 AM Stephen Smalley <[email protected]> wrote: > > On Sun, Jul 26, 2026 at 10:52 PM Thiébaud Weksteen <[email protected]> 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. > > > > These tests are skipped if libselinux does not support that feature. > > > > Signed-off-by: Thiébaud Weksteen <[email protected]> > > Acked-by: Stephen Smalley <[email protected]> This series has been merged. > > > --- > > Changes since v2: > > - Add subcommand "check" to skip test_multiple when not supported by > > libselinux. > > > > tests/file_contexts/Makefile | 2 +- > > tests/file_contexts/test | 35 +++- > > tests/file_contexts/test_multiple.c | 275 ++++++++++++++++++++++++++++ > > 3 files changed, 301 insertions(+), 11 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..6d97597 100755 > > --- a/tests/file_contexts/test > > +++ b/tests/file_contexts/test > > @@ -3,23 +3,38 @@ > > # This test validates the parsing of file_contexts. > > # > > > > -use Test; > > - > > -BEGIN { plan tests => 4; } > > - > > -$basedir = $0; > > -$basedir =~ s|(.*)/[^/]*|$1|; > > +use Test::More; > > + > > +BEGIN { > > + $basedir = $0; > > + $basedir =~ s|(.*)/[^/]*|$1|; > > + > > + $test_multiple = 0; > > + $result = system "$basedir/test_multiple $basedir check 2>/dev/null"; > > + if ( $result eq 0 ) { > > + $test_multiple = 1; > > + plan tests => 5; > > + } > > + else { > > + plan tests => 4; > > + } > > +} > > > > $result = system "$basedir/test_open $basedir 2>&1"; > > -ok( $result, 0 ); > > +ok( $result eq 0 ); > > > > $result = system "$basedir/test_lookup $basedir 2>&1"; > > -ok( $result, 0 ); > > +ok( $result eq 0 ); > > > > $result = system "$basedir/test_open_base $basedir 2>&1"; > > -ok( $result, 0 ); > > +ok( $result eq 0 ); > > > > $result = system "$basedir/test_validate $basedir 2>&1"; > > -ok( $result, 0 ); > > +ok( $result eq 0 ); > > + > > +if ($test_multiple) { > > + $result = system "$basedir/test_multiple $basedir 2>&1"; > > + ok( $result eq 0 ); > > +} > > > > exit; > > diff --git a/tests/file_contexts/test_multiple.c b/tests/file_contexts/test_multiple.c > > new file mode 100644 > > index 0000000..88b4cc8 > > --- /dev/null > > +++ b/tests/file_contexts/test_multiple.c > > @@ -0,0 +1,275 @@ > > +#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); > > +} > > + > > +/* Check if multiple paths are supported. Returns 0 if they are; 1 otherwise */ > > +static int check_multiple_path_support(const char *basedir) > > +{ > > + struct selabel_handle *hnd; > > + char *f1_path, *f2_path; > > + char *context1 = NULL, *context2 = NULL; > > + bool supported = false; > > + > > + 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) { > > + /* On older libselinux releases, the second file_context would > > + * be ignored but selabel_open should not fail. */ > > + log_err("Unable to open file backend"); > > + exit(2); > > + } > > + > > + if (selabel_lookup(hnd, &context1, "/", S_IFREG) == 0 && > > + selabel_lookup(hnd, &context2, "/base", S_IFREG) == 0) { > > + supported = true; > > + } > > + > > + free(context1); > > + free(context2); > > + selabel_close(hnd); > > + return supported ? 0 : 1; > > +} > > + > > +int main(int argc, char **argv) > > +{ > > + if (argc < 2 || argc > 3) { > > + log_err("usage: %s <basedir> [check]", argv[0]); > > + exit(1); > > + } > > + > > + if (argc == 3 && strcmp(argv[2], "check") == 0) { > > + return check_multiple_path_support(argv[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 > >