Re: [PATCH 2/2] libselinux: support multiple contexts for the file backend
Stephen Smalley <[email protected]> Tue, 21 Jul 2026 11:21:21 -0400
| Newsgroups | org.kernel.vger.selinux |
|---|---|
| Message-ID | <CAEjxPJ5pG_QaFZPgj5_3hKPhF6+xMYfwLnjpXSuR_RA=WmkJzA@mail.gmail.com> |
On Mon, Jul 20, 2026 at 10:11 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 logic is refactored into a helper function > report_dups(). A duplicate with the same specification result is not > fatal anymore, but a warning is still logged. > > Additionally, duplicate validation is moved after all files (including > .homedirs and .local) are loaded and sorted. For single file context setups, > the only difference in behavior is that duplicate validation now extends to > fc.homedirs and fc.local. > > A similar multiple files setup has been used in Android for 9+ years. > > Signed-off-by: Thiébaud Weksteen <[email protected]> > --- > libselinux/src/label_file.c | 216 ++++++++++++++++++++++-------------- > 1 file changed, 132 insertions(+), 84 deletions(-) > > diff --git a/libselinux/src/label_file.c b/libselinux/src/label_file.c > index eb2e66da..902c9a25 100644 > --- a/libselinux/src/label_file.c > +++ b/libselinux/src/label_file.c > @@ -1484,22 +1503,27 @@ static int init(struct selabel_handle *rec, const struct selinux_opt *opts, > unsigned n) > { > struct saved_data *data = rec->data; > - const char *path = NULL; > + size_t num_paths = 0, i; > + char **paths = NULL; > const char *prefix = NULL; > - int status = -1, baseonly = 0; > + int status = -1; > + bool baseonly = false, path_provided = false; > > /* Process arguments */ > - while (n) { > - n--; > - switch (opts[n].type) { > + i = n; > + while (i--) { > + switch (opts[i].type) { > case SELABEL_OPT_PATH: > - path = opts[n].value; > + if (opts[i].value) { > + num_paths++; > + path_provided = true; > + } Here we count only non-NULL values, counting from the end. > 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 +1535,44 @@ 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_paths = 1; > +#else > + selinux_log(SELINUX_ERROR, > + "No path given to file labeling backend\n"); > + goto finish; > +#endif Do we really need/want differing behavior here for different build configs? Effectively it means a different user-visible API? > + } > + > + /* Allocate the paths */ > + paths = calloc(num_paths, sizeof(*paths)); > + if (paths == NULL) { > + goto finish; > + } > + rec->spec_files = paths; > + rec->spec_files_len = num_paths; > + > + if (path_provided) { > + for (i = 0; i < n; i++) { > + switch (opts[i].type) { > + case SELABEL_OPT_PATH: > + *paths = strdup(opts[i].value); > + if (*paths == NULL) > + goto finish; > + paths++; > + break; Here we would end up calling strdup(NULL) if any of the opts[i].value are NULL instead of skipping them as above. > + default: > + break; > + } > + } > + } > + > #if !defined(BUILD_HOST) && !defined(ANDROID) > char subs_file[PATH_MAX + 1]; > /* Process local and distribution substitution files */ > - if (!path) { > + if (!path_provided) { > status = selabel_subs_init( > selinux_file_context_subs_dist_path(), rec->digest, > &data->dist_subs, &data->dist_subs_num, > @@ -1526,66 +1584,56 @@ 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(); > + rec->spec_files[0] = strdup(selinux_file_context_path()); > + if (rec->spec_files[0] == NULL) > + goto finish; > } 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; > - } > - > - rec->spec_files = calloc(1, sizeof(path)); > - 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 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_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); > + status = process_file(rec->spec_files[0], "homedirs", rec, > + prefix, rec->digest, num_paths + 1); > if (status && errno != ENOENT) > goto finish; > > - status = process_file(path, "local", rec, prefix, rec->digest, > - 2); > + status = process_file(rec->spec_files[0], "local", rec, prefix, > + rec->digest, num_paths + 2); > if (status && errno != ENOENT) > goto finish; > } > > - if (!rec->validating || !baseonly) > - sort_specs(data); > + sort_specs(data); > + > + if (rec->validating) { > + status = nodups_spec_node(rec, data->root); > + if (status) > + goto finish; > + } > > digest_gen_hash(rec->digest); > > -- > 2.55.0.229.g6434b31f56-goog >