* [PATCH v2 1/2] libselinux: support multiple spec_files
@ 2026-07-23 3:11 Thiébaud Weksteen
2026-07-23 3:11 ` [PATCH v2 2/2] libselinux: support multiple context files for the file backend Thiébaud Weksteen
0 siblings, 1 reply; 2+ messages in thread
From: Thiébaud Weksteen @ 2026-07-23 3:11 UTC (permalink / raw)
To: selinux, Stephen Smalley
Cc: James Carter, Christian Göttsche, Ondrej Mosnacek,
Thiébaud Weksteen
Update the existing backends to potentially allocate multiple
spec_files. This commit simply adds a level of indirection but does not
change any current behaviour. It will facilitate the gradual migration
of backends to multiple context files.
In selabel_fini(), compat_validate() is updated to use the first
spec_file only, for legacy validation error reporting. selabel_open() is
already performing context validation if requested against individual
file paths (see insert_spec).
Signed-off-by: Thiébaud Weksteen <tweek@google.com>
---
Changes since v1:
- Use sizeof(*rec->spec_files) instead of sizeof(path) as suggested by
Stephen.
libselinux/src/label.c | 10 ++++++++--
libselinux/src/label_db.c | 11 +++++++++--
libselinux/src/label_file.c | 8 ++++++--
libselinux/src/label_internal.h | 5 +++--
libselinux/src/label_media.c | 8 +++++++-
libselinux/src/label_x.c | 8 +++++++-
6 files changed, 40 insertions(+), 10 deletions(-)
diff --git a/libselinux/src/label.c b/libselinux/src/label.c
index 991b9769..4a97d3bc 100644
--- a/libselinux/src/label.c
+++ b/libselinux/src/label.c
@@ -162,7 +162,7 @@ static int selabel_fini(const struct selabel_handle *rec,
char *ctx_trans;
int rc;
- if (compat_validate(rec, lr, rec->spec_file, lr->lineno))
+ if (compat_validate(rec, lr, rec->spec_files[0], lr->lineno))
return -1;
if (!translating)
@@ -398,11 +398,17 @@ int selabel_digest(struct selabel_handle *rec, unsigned char **digest,
void selabel_close(struct selabel_handle *rec)
{
+ size_t i;
+
if (rec->digest)
selabel_digest_fini(rec->digest);
if (rec->func_close)
rec->func_close(rec);
- free(rec->spec_file);
+ if (rec->spec_files) {
+ for (i = 0; i < rec->spec_files_len; i++)
+ free(rec->spec_files[i]);
+ free(rec->spec_files);
+ }
free(rec);
}
diff --git a/libselinux/src/label_db.c b/libselinux/src/label_db.c
index bafa9328..53731470 100644
--- a/libselinux/src/label_db.c
+++ b/libselinux/src/label_db.c
@@ -302,12 +302,19 @@ static catalog_t *db_init(const struct selinux_opt *opts, unsigned nopts,
errno = EINVAL;
return NULL;
}
- rec->spec_file = strdup(path);
- if (!rec->spec_file) {
+ rec->spec_files = calloc(1, sizeof(*rec->spec_files));
+ if (!rec->spec_files) {
free(catalog);
fclose(filp);
return NULL;
}
+ rec->spec_files[0] = strdup(path);
+ if (!rec->spec_files[0]) {
+ free(catalog);
+ fclose(filp);
+ return NULL;
+ }
+ rec->spec_files_len = 1;
/*
* Parse for each lines
diff --git a/libselinux/src/label_file.c b/libselinux/src/label_file.c
index 0c0499eb..d91e1462 100644
--- a/libselinux/src/label_file.c
+++ b/libselinux/src/label_file.c
@@ -1549,9 +1549,13 @@ static int init(struct selabel_handle *rec, const struct selinux_opt *opts,
goto finish;
}
- rec->spec_file = strdup(path);
- if (!rec->spec_file)
+ 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
diff --git a/libselinux/src/label_internal.h b/libselinux/src/label_internal.h
index 4ff39d96..d54053df 100644
--- a/libselinux/src/label_internal.h
+++ b/libselinux/src/label_internal.h
@@ -96,10 +96,11 @@ struct selabel_handle {
void *data;
/*
- * The main spec file used. Note for file contexts the local and/or
+ * The spec files used. Note for file contexts the local and/or
* homedirs could also have been used to resolve a context.
*/
- char *spec_file;
+ size_t spec_files_len;
+ char **spec_files;
/* ptr to SHA1 hash information if SELABEL_OPT_DIGEST set */
struct selabel_digest *digest;
diff --git a/libselinux/src/label_media.c b/libselinux/src/label_media.c
index 957fcfd3..f315abc3 100644
--- a/libselinux/src/label_media.c
+++ b/libselinux/src/label_media.c
@@ -109,7 +109,13 @@ static int init(struct selabel_handle *rec, const struct selinux_opt *opts,
errno = EINVAL;
goto finish;
}
- rec->spec_file = strdup(path);
+ 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;
/*
* Perform two passes over the specification file.
diff --git a/libselinux/src/label_x.c b/libselinux/src/label_x.c
index 0c525bfc..b528a019 100644
--- a/libselinux/src/label_x.c
+++ b/libselinux/src/label_x.c
@@ -139,7 +139,13 @@ static int init(struct selabel_handle *rec, const struct selinux_opt *opts,
errno = EINVAL;
goto finish;
}
- rec->spec_file = strdup(path);
+ 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;
/*
* Perform two passes over the specification file.
--
2.55.0.229.g6434b31f56-goog
^ permalink raw reply related [flat|nested] 2+ messages in thread
* [PATCH v2 2/2] libselinux: support multiple context files for the file backend
2026-07-23 3:11 [PATCH v2 1/2] libselinux: support multiple spec_files Thiébaud Weksteen
@ 2026-07-23 3:11 ` Thiébaud Weksteen
0 siblings, 0 replies; 2+ messages in thread
From: Thiébaud Weksteen @ 2026-07-23 3:11 UTC (permalink / raw)
To: selinux, Stephen Smalley
Cc: James Carter, Christian Göttsche, Ondrej Mosnacek,
Thiébaud Weksteen
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 <tweek@google.com>
---
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 | 139 ++++++++++++++++++++++++------------
1 file changed, 92 insertions(+), 47 deletions(-)
diff --git a/libselinux/src/label_file.c b/libselinux/src/label_file.c
index d91e1462..02bd6f5d 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;
}
@@ -1484,22 +1491,30 @@ 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_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) {
+ for (i = 0; i < n; i++) {
+ switch (opts[i].type) {
case SELABEL_OPT_PATH:
- path = opts[n].value;
+ if (opts[i].value) {
+ num_paths++;
+ if (num_paths == UINT8_MAX) {
+ errno = EINVAL;
+ return -1;
+ }
+ 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 +1526,50 @@ 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");
+ errno = EINVAL;
+ return -1;
+#endif
+ }
+
+ /* If !baseonly, we reserve the last 2 indexes for homedirs and local.
+ * Let's make sure we don't wrap */
+ if (!baseonly && num_paths > UINT8_MAX - 1) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* Allocate the paths. */
+ paths = calloc(num_paths, sizeof(*paths));
+ if (paths == NULL)
+ goto finish;
+
+ rec->spec_files = paths;
+ rec->spec_files_len = num_paths;
+
+ /* Copy all the paths given. */
+ if (path_provided) {
+ for (i = 0; i < n; i++) {
+ if (opts[i].type == SELABEL_OPT_PATH && opts[i].value) {
+ *paths = strdup(opts[i].value);
+ if (*paths == NULL)
+ goto finish;
+ paths++;
+ }
+ }
+ }
+
#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 +1581,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(*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 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);
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 + 1);
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
^ permalink raw reply related [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-07-23 3:11 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-23 3:11 [PATCH v2 1/2] libselinux: support multiple spec_files Thiébaud Weksteen
2026-07-23 3:11 ` [PATCH v2 2/2] libselinux: support multiple context files for the file backend Thiébaud Weksteen
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.