Re: [PATCH v3 2/2] libselinux: support multiple context files for the file backend
Stephen Smalley <[email protected]> Mon, 27 Jul 2026 09:30:40 -0400
| Newsgroups | org.kernel.vger.selinux |
|---|---|
| Message-ID | <CAEjxPJ5bkPUXBF5ge0zChcWHQVxMX3n0dGRFsYJnuSyfk44BCg@mail.gmail.com> |
On Sun, Jul 26, 2026 at 9:39 PM Thiébaud Weksteen <[email protected]> wrote: > > Update the file labeling backend to support specifying multiple > SELABEL_OPT_PATH options in selabel_open(). > > All provided paths are stored in rec->spec_files and processed during > initialization. Derived files, such as substitutions (.subs, .subs_dist) > and auxiliary contexts (.homedirs, .local), continue to be based on the > first path (or default selinux_file_context_path()) when enabled. > > Duplicate checking is only performed within each context file. The current > behavior expects the ability for a later file to overwrite a previous > declaration (e.g., .local). > > Signed-off-by: Thiébaud Weksteen <[email protected]> Acked-by: Stephen Smalley <[email protected]> > --- > Changes since v2: > - Add optional paths (homedirs, local) to spec_files. Since these might > be referenced, let's keep track of them as any other spec files. > spec_files now contains the required files followed by the optional > files. > - Strictly validate that, at most, 255 options are provided to init(). > - Remove the intermediate `path` variable. > - Note: the 2rd argument of process_file is now always NULL. This can be > cleaned in a follow up commit. > > Changes since v1: > - Restore nodups_spec_node, ignore duplicates across files. > - Change num_paths type to uint8_t and check for overflow. > - Check for NULL before strdup any path. > - Use num_paths and num_paths+1 for homedirs and local. > > libselinux/src/label_file.c | 154 ++++++++++++++++++++++---------- > libselinux/src/label_internal.h | 3 +- > 2 files changed, 107 insertions(+), 50 deletions(-) > > diff --git a/libselinux/src/label_file.c b/libselinux/src/label_file.c > index d91e1462..c955216b 100644 > --- a/libselinux/src/label_file.c > +++ b/libselinux/src/label_file.c > @@ -106,7 +106,8 @@ void sort_spec_node(struct spec_node *node, struct spec_node *parent) > /* > * Warn about duplicate specifications. > */ > -static int nodups_spec_node(const struct spec_node *node, const char *path) > +static int nodups_spec_node(const struct selabel_handle *rec, > + const struct spec_node *node) > { > int rc = 0; > > @@ -126,13 +127,16 @@ static int nodups_spec_node(const struct spec_node *node, const char *path) > node1->file_kind != node2->file_kind) > continue; > > + if (node1->inputno != node2->inputno) > + continue; > + > rc = -1; > errno = EINVAL; > if (strcmp(node1->lr.ctx_raw, node2->lr.ctx_raw) != 0) { > COMPAT_LOG( > SELINUX_ERROR, > "%s: Multiple different specifications for %s %s (%s and %s).\n", > - path, > + rec->spec_files[node1->inputno], > file_kind_to_string(node1->file_kind), > node1->literal_match, node1->lr.ctx_raw, > node2->lr.ctx_raw); > @@ -140,7 +144,7 @@ static int nodups_spec_node(const struct spec_node *node, const char *path) > COMPAT_LOG( > SELINUX_ERROR, > "%s: Multiple same specifications for %s %s.\n", > - path, > + rec->spec_files[node1->inputno], > file_kind_to_string(node1->file_kind), > node1->literal_match); > } > @@ -168,6 +172,9 @@ static int nodups_spec_node(const struct spec_node *node, const char *path) > node1->file_kind != node2->file_kind) > continue; > > + if (node1->inputno != node2->inputno) > + continue; > + > rc = -1; > errno = EINVAL; > if (strcmp(node1->lr.ctx_raw, > @@ -175,7 +182,7 @@ static int nodups_spec_node(const struct spec_node *node, const char *path) > COMPAT_LOG( > SELINUX_ERROR, > "%s: Multiple different specifications for %s %s (%s and %s).\n", > - path, > + rec->spec_files[node1->inputno], > file_kind_to_string( > node1->file_kind), > node1->regex_str, > @@ -185,7 +192,7 @@ static int nodups_spec_node(const struct spec_node *node, const char *path) > COMPAT_LOG( > SELINUX_ERROR, > "%s: Multiple same specifications for %s %s.\n", > - path, > + rec->spec_files[node1->inputno], > file_kind_to_string( > node1->file_kind), > node1->regex_str); > @@ -197,7 +204,7 @@ static int nodups_spec_node(const struct spec_node *node, const char *path) > for (uint32_t i = 0; i < node->children_num; i++) { > int rc2; > > - rc2 = nodups_spec_node(&node->children[i], path); > + rc2 = nodups_spec_node(rec, &node->children[i]); > if (rc2) > rc = rc2; > } > @@ -1480,26 +1487,37 @@ static char *selabel_sub_key(const struct saved_data *data, const char *key, > > static void closef(struct selabel_handle *rec); > > +static const char *const opt_suffixes[] = { "homedirs", "local" }; > + > static int init(struct selabel_handle *rec, const struct selinux_opt *opts, > unsigned n) > { > struct saved_data *data = rec->data; > - const char *path = NULL; > + uint8_t num_required_paths = 0, num_optional_paths = 0, i, j; > + size_t total_paths; > const char *prefix = NULL; > - int status = -1, baseonly = 0; > + int status = -1; > + bool baseonly = false, path_provided = false; > + > + if (n > UINT8_MAX) { > + errno = EINVAL; > + return -1; > + } > > /* Process arguments */ > - while (n) { > - n--; > - switch (opts[n].type) { > + for (i = 0; i < n; i++) { > + switch (opts[i].type) { > case SELABEL_OPT_PATH: > - path = opts[n].value; > + if (opts[i].value) { > + num_required_paths++; > + path_provided = true; > + } > break; > case SELABEL_OPT_SUBSET: > - prefix = opts[n].value; > + prefix = opts[i].value; > break; > case SELABEL_OPT_BASEONLY: > - baseonly = !!opts[n].value; > + baseonly = !!opts[i].value; > break; > case SELABEL_OPT_UNUSED: > case SELABEL_OPT_VALIDATE: > @@ -1511,10 +1529,56 @@ static int init(struct selabel_handle *rec, const struct selinux_opt *opts, > } > } > > + /* If no paths were provided, we will use the default path or fail, > + * depending on the target. */ > + if (!path_provided) { > +#if !defined(BUILD_HOST) && !defined(ANDROID) > + num_required_paths = 1; > +#else > + selinux_log(SELINUX_ERROR, > + "No path given to file labeling backend\n"); > + errno = EINVAL; > + return -1; > +#endif > + } > + > + if (!baseonly) > + num_optional_paths = ARRAY_SIZE(opt_suffixes); > + > + total_paths = num_required_paths + num_optional_paths; > + > + /* Make sure total input files do not exceed the 256 indices supported > + * by uint8_t inputno */ > + if (total_paths > UINT8_MAX + 1) { > + errno = EINVAL; > + return -1; > + } > + > + /* Allocate the paths. */ > + rec->spec_files = calloc(total_paths, sizeof(*rec->spec_files)); > + if (rec->spec_files == NULL) > + goto finish; > + rec->spec_files_len = total_paths; > + > + /* Copy all the paths given. */ > + if (path_provided) { > + for (i = 0, j = 0; i < n; i++) { > + if (opts[i].type == SELABEL_OPT_PATH && opts[i].value) { > + rec->spec_files[j] = strdup(opts[i].value); > + if (rec->spec_files[j] == NULL) > + goto finish; > + j++; > + } > + } > + } > + > #if !defined(BUILD_HOST) && !defined(ANDROID) > char subs_file[PATH_MAX + 1]; > /* Process local and distribution substitution files */ > - if (!path) { > + if (!path_provided) { > + rec->spec_files[0] = strdup(selinux_file_context_path()); > + if (rec->spec_files[0] == NULL) > + goto finish; > status = selabel_subs_init( > selinux_file_context_subs_dist_path(), rec->digest, > &data->dist_subs, &data->dist_subs_num, > @@ -1526,67 +1590,61 @@ static int init(struct selabel_handle *rec, const struct selinux_opt *opts, > &data->subs_num, &data->subs_alloc); > if (status) > goto finish; > - path = selinux_file_context_path(); > } else { > - snprintf(subs_file, sizeof(subs_file), "%s.subs_dist", path); > + snprintf(subs_file, sizeof(subs_file), "%s.subs_dist", > + rec->spec_files[0]); > status = selabel_subs_init(subs_file, rec->digest, > &data->dist_subs, > &data->dist_subs_num, > &data->dist_subs_alloc); > if (status) > goto finish; > - snprintf(subs_file, sizeof(subs_file), "%s.subs", path); > + snprintf(subs_file, sizeof(subs_file), "%s.subs", > + rec->spec_files[0]); > status = selabel_subs_init(subs_file, rec->digest, &data->subs, > &data->subs_num, &data->subs_alloc); > if (status) > goto finish; > } > - > #endif > > - if (!path) { > - errno = EINVAL; > - goto finish; > + for (i = 0; i < num_optional_paths; i++) { > + if (asprintf(&rec->spec_files[num_required_paths + i], "%s.%s", > + rec->spec_files[0], opt_suffixes[i]) < 0) { > + rec->spec_files[num_required_paths + i] = NULL; > + goto finish; > + } > } > > - rec->spec_files = calloc(1, sizeof(*rec->spec_files)); > - if (!rec->spec_files) > - goto finish; > - rec->spec_files[0] = strdup(path); > - if (!rec->spec_files[0]) > - goto finish; > - rec->spec_files_len = 1; > - > /* > - * The do detailed validation of the input and fill the spec array > + * Process each main input file. > */ > - status = process_file(path, NULL, rec, prefix, rec->digest, 0); > - if (status) > - goto finish; > - > - if (rec->validating) { > - sort_specs(data); > - > - status = nodups_spec_node(data->root, path); > + for (i = 0; i < num_required_paths; i++) { > + status = process_file(rec->spec_files[i], NULL, rec, prefix, > + rec->digest, i); > if (status) > goto finish; > } > > - if (!baseonly) { > - status = process_file(path, "homedirs", rec, prefix, > - rec->digest, 1); > + /* > + * Process each optional input file. > + */ > + for (i = 0; i < num_optional_paths; i++) { > + status = process_file(rec->spec_files[num_required_paths + i], > + NULL, rec, prefix, rec->digest, > + num_required_paths + i); > if (status && errno != ENOENT) > goto finish; > + } > > - status = process_file(path, "local", rec, prefix, rec->digest, > - 2); > - if (status && errno != ENOENT) > + sort_specs(data); > + > + if (rec->validating) { > + status = nodups_spec_node(rec, data->root); > + if (status) > goto finish; > } > > - if (!rec->validating || !baseonly) > - sort_specs(data); > - > digest_gen_hash(rec->digest); > > status = 0; > diff --git a/libselinux/src/label_internal.h b/libselinux/src/label_internal.h > index d54053df..4279637e 100644 > --- a/libselinux/src/label_internal.h > +++ b/libselinux/src/label_internal.h > @@ -96,8 +96,7 @@ struct selabel_handle { > void *data; > > /* > - * The spec files used. Note for file contexts the local and/or > - * homedirs could also have been used to resolve a context. > + * The specification files used. > */ > size_t spec_files_len; > char **spec_files; > -- > 2.55.0.229.g6434b31f56-goog >