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
> >