diff --git a/0009-restorecon-Add-option-to-count-relabeled-files.patch b/0009-restorecon-Add-option-to-count-relabeled-files.patch new file mode 100644 index 0000000..299a508 --- /dev/null +++ b/0009-restorecon-Add-option-to-count-relabeled-files.patch @@ -0,0 +1,356 @@ +From 54cbd31a219790932364d8b79f9a459ea3276bcf Mon Sep 17 00:00:00 2001 +From: Vit Mojzis +Date: Fri, 7 Nov 2025 18:41:36 +0100 +Subject: [PATCH] restorecon: Add option to count relabeled files + +This is useful in case we want to check that a remediation using +restorecon was successful (otherwise 0 is always returned, even if no +files were relabeled). + +Signed-off-by: Vit Mojzis +--- + libselinux/include/selinux/restorecon.h | 15 +++++++++++ + libselinux/src/libselinux.map | 5 ++++ + libselinux/src/selinux_restorecon.c | 34 ++++++++++++++++++++++--- + policycoreutils/setfiles/restore.c | 12 ++++++--- + policycoreutils/setfiles/restore.h | 3 ++- + policycoreutils/setfiles/restorecon.8 | 3 +++ + policycoreutils/setfiles/setfiles.c | 26 ++++++++++++++----- + 7 files changed, 83 insertions(+), 15 deletions(-) + +diff --git a/libselinux/include/selinux/restorecon.h b/libselinux/include/selinux/restorecon.h +index 8dcc831b..aca218dc 100644 +--- a/libselinux/include/selinux/restorecon.h ++++ b/libselinux/include/selinux/restorecon.h +@@ -134,6 +134,11 @@ extern int selinux_restorecon_parallel(const char *pathname, + */ + #define SELINUX_RESTORECON_SET_USER_ROLE 0x40000 + ++/* ++ * Count the number of relabeled files (or would be relabeled if "nochange" was not set). ++ */ ++#define SELINUX_RESTORECON_COUNT_RELABELED 0x80000 ++ + /** + * selinux_restorecon_set_sehandle - Set the global fc handle. + * @hndl: specifies handle to set as the global fc handle. +@@ -228,6 +233,16 @@ extern int selinux_restorecon_xattr(const char *pathname, + */ + extern long unsigned selinux_restorecon_get_skipped_errors(void); + ++/* selinux_restorecon_get_relabeled_files - Get the number of relabeled files ++ * ++ * If SELINUX_RESTORECON_COUNT_RELABELED was passed to selinux_restorecon(3) or ++ * selinux_restorecon_parallel(3), this function returns the number of files ++ * that were successfully relabeled. ++ * If the SELINUX_RESTORECON_NOCHANGE flag was set, this function returns ++ * the number of files that would be relabeled. ++ */ ++extern long unsigned selinux_restorecon_get_relabeled_files(void); ++ + #ifdef __cplusplus + } + #endif +diff --git a/libselinux/src/libselinux.map b/libselinux/src/libselinux.map +index ab002f01..95cd53b0 100644 +--- a/libselinux/src/libselinux.map ++++ b/libselinux/src/libselinux.map +@@ -262,3 +262,8 @@ LIBSELINUX_3.9 { + global: + context_to_str; + } LIBSELINUX_3.8; ++ ++LIBSELINUX_3.10 { ++ global: ++ selinux_restorecon_get_relabeled_files; ++} LIBSELINUX_3.9; +diff --git a/libselinux/src/selinux_restorecon.c b/libselinux/src/selinux_restorecon.c +index 23546cb0..28749ee2 100644 +--- a/libselinux/src/selinux_restorecon.c ++++ b/libselinux/src/selinux_restorecon.c +@@ -69,6 +69,9 @@ static struct dir_xattr *dir_xattr_last; + /* Number of errors ignored during the file tree walk. */ + static long unsigned skipped_errors; + ++/* Number of successfully relabeled files or files that would be relabeled */ ++static long unsigned relabeled_files; ++ + /* restorecon_flags for passing to restorecon_sb() */ + struct rest_flags { + bool nochange; +@@ -88,6 +91,7 @@ struct rest_flags { + bool warnonnomatch; + bool conflicterror; + bool count_errors; ++ bool count_relabeled; + }; + + static void restorecon_init(void) +@@ -650,11 +654,12 @@ out: + } + + static int restorecon_sb(const char *pathname, const struct stat *sb, +- const struct rest_flags *flags, bool first) ++ const struct rest_flags *flags, bool first, bool *updated_out) + { + char *newcon = NULL; + char *curcon = NULL; + int rc; ++ bool updated = false; + const char *lookup_path = pathname; + + if (rootpath) { +@@ -736,7 +741,6 @@ static int restorecon_sb(const char *pathname, const struct stat *sb, + } + + if (curcon == NULL || strcmp(curcon, newcon) != 0) { +- bool updated = false; + + if (!flags->set_specctx && curcon && + (is_context_customizable(curcon) > 0)) { +@@ -796,9 +800,14 @@ static int restorecon_sb(const char *pathname, const struct stat *sb, + syslog(LOG_INFO, "labeling %s to %s\n", + pathname, newcon); + } ++ ++ /* Note: relabel counting handled by caller */ ++ + } + + out: ++ if (updated_out) ++ *updated_out = updated; + rc = 0; + out1: + freecon(curcon); +@@ -887,6 +896,7 @@ struct rest_state { + bool abort; + int error; + long unsigned skipped_errors; ++ long unsigned relabeled_files; + int saved_errno; + pthread_mutex_t mutex; + }; +@@ -1010,8 +1020,9 @@ loop_body: + if (state->parallel) + pthread_mutex_unlock(&state->mutex); + ++ bool updated = false; + error = restorecon_sb(ent_path, &ent_st, &state->flags, +- first); ++ first, &updated); + + if (state->parallel) { + pthread_mutex_lock(&state->mutex); +@@ -1030,6 +1041,8 @@ loop_body: + state->skipped_errors++; + else + state->error = error; ++ } else if (updated && state->flags.count_relabeled) { ++ state->relabeled_files++; + } + break; + } +@@ -1087,6 +1100,8 @@ static int selinux_restorecon_common(const char *pathname_orig, + SELINUX_RESTORECON_IGNORE_DIGEST) ? true : false; + state.flags.count_errors = (restorecon_flags & + SELINUX_RESTORECON_COUNT_ERRORS) ? true : false; ++ state.flags.count_relabeled = (restorecon_flags & ++ SELINUX_RESTORECON_COUNT_RELABELED) ? true : false; + state.setrestorecondigest = true; + + state.head = NULL; +@@ -1094,6 +1109,7 @@ static int selinux_restorecon_common(const char *pathname_orig, + state.abort = false; + state.error = 0; + state.skipped_errors = 0; ++ state.relabeled_files = 0; + state.saved_errno = 0; + + struct stat sb; +@@ -1215,7 +1231,11 @@ static int selinux_restorecon_common(const char *pathname_orig, + goto cleanup; + } + +- error = restorecon_sb(pathname, &sb, &state.flags, true); ++ bool updated = false; ++ error = restorecon_sb(pathname, &sb, &state.flags, true, &updated); ++ if (updated && state.flags.count_relabeled) { ++ state.relabeled_files++; ++ } + goto cleanup; + } + +@@ -1341,6 +1361,7 @@ out: + (void) fts_close(state.fts); + errno = state.saved_errno; + cleanup: ++ relabeled_files = state.relabeled_files; + if (state.flags.add_assoc) { + if (state.flags.verbose) + filespec_eval(); +@@ -1618,3 +1639,8 @@ long unsigned selinux_restorecon_get_skipped_errors(void) + { + return skipped_errors; + } ++ ++long unsigned selinux_restorecon_get_relabeled_files(void) ++{ ++ return relabeled_files; ++} +diff --git a/policycoreutils/setfiles/restore.c b/policycoreutils/setfiles/restore.c +index 2c031ccc..07582e7c 100644 +--- a/policycoreutils/setfiles/restore.c ++++ b/policycoreutils/setfiles/restore.c +@@ -43,7 +43,7 @@ void restore_init(struct restore_opts *opts) + opts->syslog_changes | opts->log_matches | + opts->ignore_noent | opts->ignore_mounts | + opts->mass_relabel | opts->conflict_error | +- opts->count_errors; ++ opts->count_errors | opts->count_relabeled; + + /* Use setfiles, restorecon and restorecond own handles */ + selinux_restorecon_set_sehandle(opts->hnd); +@@ -75,7 +75,7 @@ void restore_finish(void) + } + + int process_glob(char *name, struct restore_opts *opts, size_t nthreads, +- long unsigned *skipped_errors) ++ long unsigned *skipped_errors, long unsigned *relabeled_files) + { + glob_t globbuf; + size_t i, len; +@@ -99,8 +99,12 @@ int process_glob(char *name, struct restore_opts *opts, size_t nthreads, + nthreads); + if (rc < 0) + errors = rc; +- else if (opts->restorecon_flags & SELINUX_RESTORECON_COUNT_ERRORS) +- *skipped_errors += selinux_restorecon_get_skipped_errors(); ++ else { ++ if (opts->restorecon_flags & SELINUX_RESTORECON_COUNT_ERRORS) ++ *skipped_errors += selinux_restorecon_get_skipped_errors(); ++ if (opts->restorecon_flags & SELINUX_RESTORECON_COUNT_RELABELED) ++ *relabeled_files += selinux_restorecon_get_relabeled_files(); ++ } + } + + globfree(&globbuf); +diff --git a/policycoreutils/setfiles/restore.h b/policycoreutils/setfiles/restore.h +index 95afb960..36f73059 100644 +--- a/policycoreutils/setfiles/restore.h ++++ b/policycoreutils/setfiles/restore.h +@@ -37,6 +37,7 @@ struct restore_opts { + unsigned int ignore_mounts; + unsigned int conflict_error; + unsigned int count_errors; ++ unsigned int count_relabeled; + /* restorecon_flags holds | of above for restore_init() */ + unsigned int restorecon_flags; + char *rootpath; +@@ -52,7 +53,7 @@ void restore_init(struct restore_opts *opts); + void restore_finish(void); + void add_exclude(const char *directory); + int process_glob(char *name, struct restore_opts *opts, size_t nthreads, +- long unsigned *skipped_errors); ++ long unsigned *skipped_errors, long unsigned *relabeled_files); + extern char **exclude_list; + + #endif +diff --git a/policycoreutils/setfiles/restorecon.8 b/policycoreutils/setfiles/restorecon.8 +index 77dd0542..66b9c800 100644 +--- a/policycoreutils/setfiles/restorecon.8 ++++ b/policycoreutils/setfiles/restorecon.8 +@@ -153,6 +153,9 @@ display warnings about entries that had no matching files by outputting the + .BR selabel_stats (3) + results. + .TP ++.B \-c ++count and display the number of (would be) relabeled files. The exit code will be set to 0 only if at least one file is relabeled. ++.TP + .B \-0 + the separator for the input items is assumed to be the null character + (instead of the white space). The quotes and the backslash characters are +diff --git a/policycoreutils/setfiles/setfiles.c b/policycoreutils/setfiles/setfiles.c +index 31034316..351940f3 100644 +--- a/policycoreutils/setfiles/setfiles.c ++++ b/policycoreutils/setfiles/setfiles.c +@@ -35,8 +35,8 @@ static __attribute__((__noreturn__)) void usage(const char *const name) + { + if (iamrestorecon) { + fprintf(stderr, +- "usage: %s [-iIDFUmnprRv0xT] [-e excludedir] pathname...\n" +- "usage: %s [-iIDFUmnprRv0xT] [-e excludedir] -f filename\n", ++ "usage: %s [-ciIDFUmnprRv0xT] [-e excludedir] pathname...\n" ++ "usage: %s [-ciIDFUmnprRv0xT] [-e excludedir] -f filename\n", + name, name); + } else { + fprintf(stderr, +@@ -146,11 +146,12 @@ int main(int argc, char **argv) + size_t buf_len, nthreads = 1; + const char *base; + int errors = 0; +- const char *ropts = "e:f:hiIDlmno:pqrsvFURW0xT:"; ++ const char *ropts = "ce:f:hiIDlmno:pqrsvFURW0xT:"; + const char *sopts = "c:de:f:hiIDlmno:pqr:svACEFUR:W0T:"; + const char *opts; + union selinux_callback cb; + long unsigned skipped_errors; ++ long unsigned relabeled_files; + + /* Initialize variables */ + memset(&r_opts, 0, sizeof(r_opts)); +@@ -160,6 +161,7 @@ int main(int argc, char **argv) + request_digest = 0; + policyfile = NULL; + skipped_errors = 0; ++ relabeled_files = 0; + + if (!argv[0]) { + fprintf(stderr, "Called without required program name!\n"); +@@ -223,7 +225,10 @@ int main(int argc, char **argv) + while ((opt = getopt(argc, argv, opts)) > 0) { + switch (opt) { + case 'c': +- { ++ if (iamrestorecon) { ++ r_opts.count_relabeled = SELINUX_RESTORECON_COUNT_RELABELED; ++ break; ++ } else { + FILE *policystream; + + policyfile = optarg; +@@ -457,14 +462,14 @@ int main(int argc, char **argv) + if (!strcmp(buf, "/")) + r_opts.mass_relabel = SELINUX_RESTORECON_MASS_RELABEL; + errors |= process_glob(buf, &r_opts, nthreads, +- &skipped_errors) < 0; ++ &skipped_errors, &relabeled_files) < 0; + } + if (strcmp(input_filename, "-") != 0) + fclose(f); + } else { + for (i = optind; i < argc; i++) + errors |= process_glob(argv[i], &r_opts, nthreads, +- &skipped_errors) < 0; ++ &skipped_errors, &relabeled_files) < 0; + } + + if (r_opts.mass_relabel && !r_opts.nochange) +@@ -479,5 +484,14 @@ int main(int argc, char **argv) + if (r_opts.progress) + fprintf(stdout, "\n"); + ++ /* Output relabeled file count if requested */ ++ if (r_opts.count_relabeled) { ++ long unsigned relabeled_count = selinux_restorecon_get_relabeled_files(); ++ printf("Relabeled %lu files\n", relabeled_count); ++ ++ /* Set exit code to 0 if at least one file was relabeled */ ++ exit(errors ? -1 : relabeled_count ? 0 : 1); ++ } ++ + exit(errors ? -1 : skipped_errors ? 1 : 0); + } +-- +2.52.0 + diff --git a/changelog b/changelog index 2ac5556..7aed3c5 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,6 @@ +* Mon Jan 26 2026 Vit Mojzis - 3.9-3 +- restorecon: Add option to count relabeled files (RHEL-94827) + * Tue Oct 07 2025 Vit Mojzis - 3.9-2 - semanage: Reset active value when deleting boolean customizations (RHEL-111421) - setfiles: Add -A option to disable SELINUX_RESTORECON_ADD_ASSOC (RHEL-111505) diff --git a/policycoreutils.spec b/policycoreutils.spec index 4cc1551..1aa2e07 100644 --- a/policycoreutils.spec +++ b/policycoreutils.spec @@ -1,7 +1,7 @@ %global libauditver 3.0 %global libsepolver 3.9-1 %global libsemanagever 3.9-1 -%global libselinuxver 3.9-1 +%global libselinuxver 3.9-4 %global generatorsdir %{_prefix}/lib/systemd/system-generators @@ -11,7 +11,7 @@ Summary: SELinux policy core utilities Name: policycoreutils Version: 3.9 -Release: 2%{?dist} +Release: 3%{?dist} License: GPL-2.0-or-later # https://github.com/SELinuxProject/selinux/wiki/Releases Source0: https://github.com/SELinuxProject/selinux/releases/download/%{version}/selinux-%{version}.tar.gz @@ -46,6 +46,8 @@ Patch0005: 0005-python-sepolicy-Fix-spec-file-dependencies.patch Patch0006: 0006-sepolicy-Fix-detection-of-writeable-locations.patch Patch0007: 0007-setfiles-Add-A-option-to-disable-SELINUX_RESTORECON_.patch Patch0008: 0008-semanage-Reset-active-value-when-deleting-boolean-cu.patch +Patch0009: 0009-restorecon-Add-option-to-count-relabeled-files.patch +# Patch0009 ^^^ needs to also contain diff for libselinux because of updated header files # Patch list end Obsoletes: policycoreutils < 2.0.61-2