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

Stephen Smalley <[email protected]> Thu, 23 Jul 2026 12:24:29 -0400
Newsgroups org.kernel.vger.selinux
Message-ID <CAEjxPJ4YWcccS-rFme4LxqBQbZff-ZafkKQzskrpaqVy+2N1cQ@mail.gmail.com>
On Wed, Jul 22, 2026 at 11:13 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.
>
> Signed-off-by: Thiébaud Weksteen <[email protected]>

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
>