Re: [PATCH testsuite v3 2/2] tests/file_contexts: add tests for multiple SELABEL_OPT_PATH

Stephen Smalley <[email protected]> Mon, 27 Jul 2026 09:31:47 -0400
Newsgroups org.kernel.vger.selinux
Message-ID <CAEjxPJ69dbMJsyJYuSsfqt6CUz+YUND4weBteEwxzrJ_R3F-ZQ@mail.gmail.com>
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]>

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