[PATCH 2/2] libselinux: support multiple contexts for the file backend
"Thiébaud Weksteen" <[email protected]>
| Newsgroups | org.kernel.vger.selinux |
|---|---|
| Message-ID | <[email protected]> |
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 @@ -104,9 +104,59 @@ void sort_spec_node(struct spec_node *node, struct spec_node *parent) } /* - * Warn about duplicate specifications. + * Report a duplicate in the log. + * + * If the duplicate is considered fatal (i.e., if the specifications of both + * entries do not match), return -1 and set errno to EINVAL. + */ +static int report_dups(struct selabel_handle *rec, uint8_t inputno1, + uint8_t inputno2, const char *ctx_raw1, + const char *ctx_raw2, const char *match, + const char *file_kind) +{ + if (inputno1 >= rec->spec_files_len) + inputno1 = 0; + if (inputno2 >= rec->spec_files_len) + inputno2 = 0; + + const char *path1 = rec->spec_files[inputno1]; + const char *path2 = rec->spec_files[inputno2]; + + if (strcmp(ctx_raw1, ctx_raw2) != 0) { + if (inputno1 == inputno2) + COMPAT_LOG( + SELINUX_ERROR, + "Multiple different specifications for %s %s in %s (%s and %s).\n", + file_kind, match, path1, ctx_raw1, ctx_raw2); + else + COMPAT_LOG( + SELINUX_ERROR, + "Multiple different specifications for %s %s in %s and %s (%s and %s).\n", + file_kind, match, path1, path2, ctx_raw1, + ctx_raw2); + errno = EINVAL; + return -1; + + } else { + if (inputno1 == inputno2) + COMPAT_LOG( + SELINUX_WARNING, + "Multiple same specifications for %s %s in %s.\n", + file_kind, match, path1); + else + COMPAT_LOG( + SELINUX_WARNING, + "Multiple same specifications for %s %s in %s and %s.\n", + file_kind, match, path1, path2); + } + return 0; +} + +/* + * Find duplicate specifications. */ -static int nodups_spec_node(const struct spec_node *node, const char *path) +static int nodups_spec_node(struct selabel_handle *rec, + const struct spec_node *node) { int rc = 0; @@ -126,24 +176,10 @@ static int nodups_spec_node(const struct spec_node *node, const char *path) node1->file_kind != node2->file_kind) 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, - file_kind_to_string(node1->file_kind), - node1->literal_match, node1->lr.ctx_raw, - node2->lr.ctx_raw); - } else { - COMPAT_LOG( - SELINUX_ERROR, - "%s: Multiple same specifications for %s %s.\n", - path, - file_kind_to_string(node1->file_kind), - node1->literal_match); - } + rc = report_dups(rec, node1->inputno, node2->inputno, + node1->lr.ctx_raw, node2->lr.ctx_raw, + node1->literal_match, + file_kind_to_string(node1->file_kind)); } } @@ -168,28 +204,11 @@ static int nodups_spec_node(const struct spec_node *node, const char *path) node1->file_kind != node2->file_kind) 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, - file_kind_to_string( - node1->file_kind), - node1->regex_str, - node1->lr.ctx_raw, - node2->lr.ctx_raw); - } else { - COMPAT_LOG( - SELINUX_ERROR, - "%s: Multiple same specifications for %s %s.\n", - path, - file_kind_to_string( - node1->file_kind), - node1->regex_str); - } + rc = report_dups( + rec, node1->inputno, node2->inputno, + node1->lr.ctx_raw, node2->lr.ctx_raw, + node1->regex_str, + file_kind_to_string(node1->file_kind)); } } } @@ -197,7 +216,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 +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; + } 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 + } + + /* 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; + 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