diff --git a/0002-label_file-fix-a-data-race.patch b/0002-label_file-fix-a-data-race.patch new file mode 100644 index 0000000..0554c37 --- /dev/null +++ b/0002-label_file-fix-a-data-race.patch @@ -0,0 +1,73 @@ +From 5844f389429f26a0a62a65561fa3006feaaf6f3b Mon Sep 17 00:00:00 2001 +From: Ondrej Mosnacek +Date: Tue, 26 Oct 2021 13:52:32 +0200 +Subject: [PATCH] label_file: fix a data race + +The 'matches' member of 'struct spec' may be written to by different +threads, so it needs to be accessed using the proper atomic constructs. +Since the actual count of matches doesn't matter and is not used, +convert this field to a bool and just atomically set/read it using GCC +__atomic builtins (which are already being used in another place). + +If the compiler lacks support for __atomic builtins (which seem to have +been introduced in GCC 4.1), just fail the compilation. I don't think +it's worth tryin to invent a workaround to support a 15 years old +compiler. + +Signed-off-by: Ondrej Mosnacek +--- + libselinux/src/label_file.c | 15 +++++++++++++-- + libselinux/src/label_file.h | 2 +- + 2 files changed, 14 insertions(+), 3 deletions(-) + +diff --git a/libselinux/src/label_file.c b/libselinux/src/label_file.c +index c1306c9979e7..33d395e414f0 100644 +--- a/libselinux/src/label_file.c ++++ b/libselinux/src/label_file.c +@@ -951,7 +951,12 @@ static struct spec **lookup_all(struct selabel_handle *rec, + rc = regex_match(spec->regex, key, partial); + if (rc == REGEX_MATCH || (partial && rc == REGEX_MATCH_PARTIAL)) { + if (rc == REGEX_MATCH) { +- spec->matches++; ++#ifdef __ATOMIC_RELAXED ++ __atomic_store_n(&spec->any_matches, ++ true, __ATOMIC_RELAXED); ++#else ++#error "Please use a compiler that supports __atomic builtins" ++#endif + } + + if (strcmp(spec_arr[i].lr.ctx_raw, "<>") == 0) { +@@ -1249,9 +1254,15 @@ static void stats(struct selabel_handle *rec) + struct saved_data *data = (struct saved_data *)rec->data; + unsigned int i, nspec = data->nspec; + struct spec *spec_arr = data->spec_arr; ++ bool any_matches; + + for (i = 0; i < nspec; i++) { +- if (spec_arr[i].matches == 0) { ++#ifdef __ATOMIC_RELAXED ++ any_matches = __atomic_load_n(&spec_arr[i].any_matches, __ATOMIC_RELAXED); ++#else ++#error "Please use a compiler that supports __atomic builtins" ++#endif ++ if (!any_matches) { + if (spec_arr[i].type_str) { + COMPAT_LOG(SELINUX_WARNING, + "Warning! No matches for (%s, %s, %s)\n", +diff --git a/libselinux/src/label_file.h b/libselinux/src/label_file.h +index 343ffc705e43..b453e13f8075 100644 +--- a/libselinux/src/label_file.h ++++ b/libselinux/src/label_file.h +@@ -51,7 +51,7 @@ struct spec { + bool regex_compiled; /* bool to indicate if the regex is compiled */ + pthread_mutex_t regex_lock; /* lock for lazy compilation of regex */ + mode_t mode; /* mode format value */ +- int matches; /* number of matching pathnames */ ++ bool any_matches; /* did any pathname match? */ + int stem_id; /* indicates which stem-compression item */ + char hasMetaChars; /* regular expression has meta-chars */ + char from_mmap; /* this spec is from an mmap of the data */ +-- +2.33.1 + diff --git a/0003-selinux_restorecon-simplify-fl_head-allocation-by-us.patch b/0003-selinux_restorecon-simplify-fl_head-allocation-by-us.patch new file mode 100644 index 0000000..01c6d25 --- /dev/null +++ b/0003-selinux_restorecon-simplify-fl_head-allocation-by-us.patch @@ -0,0 +1,30 @@ +From 5dd3a11842c08a25a0f7ab798ce85710fe1e8f1f Mon Sep 17 00:00:00 2001 +From: Ondrej Mosnacek +Date: Tue, 26 Oct 2021 13:52:33 +0200 +Subject: [PATCH] selinux_restorecon: simplify fl_head allocation by using + calloc() + +Signed-off-by: Ondrej Mosnacek +--- + libselinux/src/selinux_restorecon.c | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/libselinux/src/selinux_restorecon.c b/libselinux/src/selinux_restorecon.c +index 100c77108a27..e29a2c390182 100644 +--- a/libselinux/src/selinux_restorecon.c ++++ b/libselinux/src/selinux_restorecon.c +@@ -425,10 +425,9 @@ static int filespec_add(ino_t ino, const char *con, const char *file, + struct stat64 sb; + + if (!fl_head) { +- fl_head = malloc(sizeof(file_spec_t) * HASH_BUCKETS); ++ fl_head = calloc(HASH_BUCKETS, sizeof(file_spec_t)); + if (!fl_head) + goto oom; +- memset(fl_head, 0, sizeof(file_spec_t) * HASH_BUCKETS); + } + + h = (ino + (ino >> HASH_BITS)) & HASH_MASK; +-- +2.33.1 + diff --git a/0004-selinux_restorecon-protect-file_spec-list-with-a-mut.patch b/0004-selinux_restorecon-protect-file_spec-list-with-a-mut.patch new file mode 100644 index 0000000..1143f33 --- /dev/null +++ b/0004-selinux_restorecon-protect-file_spec-list-with-a-mut.patch @@ -0,0 +1,81 @@ +From 4598a46c5ed12248a3a6e1dbe1b5a3dca52bacac Mon Sep 17 00:00:00 2001 +From: Ondrej Mosnacek +Date: Tue, 26 Oct 2021 13:52:34 +0200 +Subject: [PATCH] selinux_restorecon: protect file_spec list with a mutex + +Not very useful on its own, but will allow to implement a parallel +version of selinux_restorecon() in subsequent patches. + +Signed-off-by: Ondrej Mosnacek +--- + libselinux/src/selinux_restorecon.c | 16 ++++++++++++++-- + 1 file changed, 14 insertions(+), 2 deletions(-) + +diff --git a/libselinux/src/selinux_restorecon.c b/libselinux/src/selinux_restorecon.c +index e29a2c390182..43acbace309d 100644 +--- a/libselinux/src/selinux_restorecon.c ++++ b/libselinux/src/selinux_restorecon.c +@@ -411,6 +411,7 @@ typedef struct file_spec { + } file_spec_t; + + static file_spec_t *fl_head; ++static pthread_mutex_t fl_mutex = PTHREAD_MUTEX_INITIALIZER; + + /* + * Try to add an association between an inode and a context. If there is a +@@ -424,6 +425,8 @@ static int filespec_add(ino_t ino, const char *con, const char *file, + int h, ret; + struct stat64 sb; + ++ __pthread_mutex_lock(&fl_mutex); ++ + if (!fl_head) { + fl_head = calloc(HASH_BUCKETS, sizeof(file_spec_t)); + if (!fl_head) +@@ -444,11 +447,11 @@ static int filespec_add(ino_t ino, const char *con, const char *file, + fl->con = strdup(con); + if (!fl->con) + goto oom; +- return 1; ++ goto unlock_1; + } + + if (strcmp(fl->con, con) == 0) +- return 1; ++ goto unlock_1; + + selinux_log(SELINUX_ERROR, + "conflicting specifications for %s and %s, using %s.\n", +@@ -457,6 +460,9 @@ static int filespec_add(ino_t ino, const char *con, const char *file, + fl->file = strdup(file); + if (!fl->file) + goto oom; ++ ++ __pthread_mutex_unlock(&fl_mutex); ++ + if (flags->conflicterror) { + selinux_log(SELINUX_ERROR, + "treating conflicting specifications as an error.\n"); +@@ -481,13 +487,19 @@ static int filespec_add(ino_t ino, const char *con, const char *file, + goto oom_freefl; + fl->next = prevfl->next; + prevfl->next = fl; ++ ++ __pthread_mutex_unlock(&fl_mutex); + return 0; + + oom_freefl: + free(fl); + oom: ++ __pthread_mutex_unlock(&fl_mutex); + selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __func__); + return -1; ++unlock_1: ++ __pthread_mutex_unlock(&fl_mutex); ++ return 1; + } + + /* +-- +2.33.1 + diff --git a/0005-libselinux-make-selinux_log-thread-safe.patch b/0005-libselinux-make-selinux_log-thread-safe.patch new file mode 100644 index 0000000..078d5b4 --- /dev/null +++ b/0005-libselinux-make-selinux_log-thread-safe.patch @@ -0,0 +1,88 @@ +From c2e4cf5b21e8c775c669f3933d25a0946774ec0d Mon Sep 17 00:00:00 2001 +From: Ondrej Mosnacek +Date: Tue, 26 Oct 2021 13:52:35 +0200 +Subject: [PATCH] libselinux: make selinux_log() thread-safe + +Ensure that selinux_log() is thread-safe by guarding the call to the +underlying callback with a mutex. + +Signed-off-by: Ondrej Mosnacek +--- + libselinux/src/callbacks.c | 8 +++++--- + libselinux/src/callbacks.h | 13 ++++++++++++- + 2 files changed, 17 insertions(+), 4 deletions(-) + +diff --git a/libselinux/src/callbacks.c b/libselinux/src/callbacks.c +index c18ccc54754a..469c4055f4d7 100644 +--- a/libselinux/src/callbacks.c ++++ b/libselinux/src/callbacks.c +@@ -10,6 +10,8 @@ + #include + #include "callbacks.h" + ++pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER; ++ + /* default implementations */ + static int __attribute__ ((format(printf, 2, 3))) + default_selinux_log(int type __attribute__((unused)), const char *fmt, ...) +@@ -56,7 +58,7 @@ default_selinux_policyload(int seqno __attribute__((unused))) + + /* callback pointers */ + int __attribute__ ((format(printf, 2, 3))) +-(*selinux_log)(int, const char *, ...) = ++(*selinux_log_direct)(int, const char *, ...) = + default_selinux_log; + + int +@@ -81,7 +83,7 @@ selinux_set_callback(int type, union selinux_callback cb) + { + switch (type) { + case SELINUX_CB_LOG: +- selinux_log = cb.func_log; ++ selinux_log_direct = cb.func_log; + break; + case SELINUX_CB_AUDIT: + selinux_audit = cb.func_audit; +@@ -106,7 +108,7 @@ selinux_get_callback(int type) + + switch (type) { + case SELINUX_CB_LOG: +- cb.func_log = selinux_log; ++ cb.func_log = selinux_log_direct; + break; + case SELINUX_CB_AUDIT: + cb.func_audit = selinux_audit; +diff --git a/libselinux/src/callbacks.h b/libselinux/src/callbacks.h +index 03d87f0cbdfe..f4dab15789f9 100644 +--- a/libselinux/src/callbacks.h ++++ b/libselinux/src/callbacks.h +@@ -10,9 +10,11 @@ + #include + #include + ++#include "selinux_internal.h" ++ + /* callback pointers */ + extern int __attribute__ ((format(printf, 2, 3))) +-(*selinux_log) (int type, const char *, ...) ; ++(*selinux_log_direct) (int type, const char *, ...) ; + + extern int + (*selinux_audit) (void *, security_class_t, char *, size_t) ; +@@ -26,4 +28,13 @@ extern int + extern int + (*selinux_netlink_policyload) (int seqno) ; + ++/* Thread-safe selinux_log() function */ ++extern pthread_mutex_t log_mutex; ++ ++#define selinux_log(type, ...) do { \ ++ __pthread_mutex_lock(&log_mutex); \ ++ selinux_log_direct(type, __VA_ARGS__); \ ++ __pthread_mutex_unlock(&log_mutex); \ ++} while(0) ++ + #endif /* _SELINUX_CALLBACKS_H_ */ +-- +2.33.1 + diff --git a/0006-libselinux-make-is_context_customizable-thread-safe.patch b/0006-libselinux-make-is_context_customizable-thread-safe.patch new file mode 100644 index 0000000..3dc8213 --- /dev/null +++ b/0006-libselinux-make-is_context_customizable-thread-safe.patch @@ -0,0 +1,81 @@ +From 9a8db9356c07d16a9337df416a3261c0527afeb7 Mon Sep 17 00:00:00 2001 +From: Ondrej Mosnacek +Date: Tue, 26 Oct 2021 13:52:36 +0200 +Subject: [PATCH] libselinux: make is_context_customizable() thread-safe +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Use the __selinux_once() macro to ensure that threads don't race to +initialize the list of customizable types. + +Reported-by: Christian Göttsche +Signed-off-by: Ondrej Mosnacek +Tested-by: Christian Göttsche +--- + libselinux/src/is_customizable_type.c | 23 +++++++++++------------ + 1 file changed, 11 insertions(+), 12 deletions(-) + +diff --git a/libselinux/src/is_customizable_type.c b/libselinux/src/is_customizable_type.c +index 1b17860c3622..f83e1e83e944 100644 +--- a/libselinux/src/is_customizable_type.c ++++ b/libselinux/src/is_customizable_type.c +@@ -9,7 +9,10 @@ + #include "selinux_internal.h" + #include "context_internal.h" + +-static int get_customizable_type_list(char *** retlist) ++static char **customizable_list = NULL; ++static pthread_once_t customizable_once = PTHREAD_ONCE_INIT; ++ ++static void customizable_init(void) + { + FILE *fp; + char *buf; +@@ -18,12 +21,12 @@ static int get_customizable_type_list(char *** retlist) + + fp = fopen(selinux_customizable_types_path(), "re"); + if (!fp) +- return -1; ++ return; + + buf = malloc(selinux_page_size); + if (!buf) { + fclose(fp); +- return -1; ++ return; + } + while (fgets_unlocked(buf, selinux_page_size, fp) && ctr < UINT_MAX) { + ctr++; +@@ -54,23 +57,19 @@ static int get_customizable_type_list(char *** retlist) + fclose(fp); + free(buf); + if (!list) +- return -1; +- *retlist = list; +- return 0; ++ return; ++ customizable_list = list; + } + +-static char **customizable_list = NULL; +- + int is_context_customizable(const char * scontext) + { + int i; + const char *type; + context_t c; + +- if (!customizable_list) { +- if (get_customizable_type_list(&customizable_list) != 0) +- return -1; +- } ++ __selinux_once(customizable_once, customizable_init); ++ if (!customizable_list) ++ return -1; + + c = context_new(scontext); + if (!c) +-- +2.33.1 + diff --git a/0007-selinux_restorecon-add-a-global-mutex-to-synchronize.patch b/0007-selinux_restorecon-add-a-global-mutex-to-synchronize.patch new file mode 100644 index 0000000..a5affd5 --- /dev/null +++ b/0007-selinux_restorecon-add-a-global-mutex-to-synchronize.patch @@ -0,0 +1,45 @@ +From 73310c9694724b3ef54bbf3a3193dbb0a68ecc3b Mon Sep 17 00:00:00 2001 +From: Ondrej Mosnacek +Date: Tue, 26 Oct 2021 13:52:37 +0200 +Subject: [PATCH] selinux_restorecon: add a global mutex to synchronize + progress output + +Another small incremental change to pave the way for a parallel +selinux_restorecon() function. + +Signed-off-by: Ondrej Mosnacek +--- + libselinux/src/selinux_restorecon.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/libselinux/src/selinux_restorecon.c b/libselinux/src/selinux_restorecon.c +index 43acbace309d..169dfe3ae232 100644 +--- a/libselinux/src/selinux_restorecon.c ++++ b/libselinux/src/selinux_restorecon.c +@@ -60,6 +60,7 @@ static int exclude_count = 0; + static struct edir *exclude_lst = NULL; + static uint64_t fc_count = 0; /* Number of files processed so far */ + static uint64_t efile_count; /* Estimated total number of files */ ++static pthread_mutex_t progress_mutex = PTHREAD_MUTEX_INITIALIZER; + + /* Store information on directories with xattr's. */ + static struct dir_xattr *dir_xattr_list; +@@ -647,6 +648,7 @@ static int restorecon_sb(const char *pathname, const struct stat *sb, + } + + if (flags->progress) { ++ __pthread_mutex_lock(&progress_mutex); + fc_count++; + if (fc_count % STAR_COUNT == 0) { + if (flags->mass_relabel && efile_count > 0) { +@@ -658,6 +660,7 @@ static int restorecon_sb(const char *pathname, const struct stat *sb, + } + fflush(stdout); + } ++ __pthread_mutex_unlock(&progress_mutex); + } + + if (flags->add_assoc) { +-- +2.33.1 + diff --git a/0008-selinux_restorecon-introduce-selinux_restorecon_para.patch b/0008-selinux_restorecon-introduce-selinux_restorecon_para.patch new file mode 100644 index 0000000..a16023e --- /dev/null +++ b/0008-selinux_restorecon-introduce-selinux_restorecon_para.patch @@ -0,0 +1,800 @@ +From 847282ce385a4fc03092eb10422b1878590e9bdd Mon Sep 17 00:00:00 2001 +From: Ondrej Mosnacek +Date: Tue, 26 Oct 2021 13:52:38 +0200 +Subject: [PATCH] selinux_restorecon: introduce selinux_restorecon_parallel(3) + +Refactor selinux_restorecon(3) to allow for distributing the relabeling +to multiple threads and add a new function +selinux_restorecon_parallel(3), which allows specifying the number of +threads to use. The existing selinux_restorecon(3) function maintains +the same interface and maintains the same behavior (i.e. relabeling is +done on a single thread). + +The parallel implementation takes a simple approach of performing all +the directory tree traversal in a critical section and only letting the +relabeling of individual objects run in parallel. Thankfully, this +approach turns out to be efficient enough in practice, as shown by +restorecon benchmarks (detailed in a subsequent patch that switches +setfiles & restorecon to use selinux_restorecon_parallel(3)). + +Note that to be able to use the parallelism, the calling application/ +library must be explicitly linked to the libpthread library (statically +or dynamically). This is necessary to mantain the requirement that +libselinux shouldn't explicitly link with libpthread. (I don't know what +exactly was the reason behind this requirement as the commit logs are +fuzzy, but special care has been taken in the past to maintain it, so I +didn't want to break it...) + +Signed-off-by: Ondrej Mosnacek +--- + libselinux/include/selinux/restorecon.h | 14 + + libselinux/man/man3/selinux_restorecon.3 | 29 ++ + .../man/man3/selinux_restorecon_parallel.3 | 1 + + libselinux/src/libselinux.map | 5 + + libselinux/src/selinux_internal.h | 16 + + libselinux/src/selinux_restorecon.c | 436 ++++++++++++------ + libselinux/src/selinuxswig_python.i | 6 +- + libselinux/src/selinuxswig_python_exception.i | 8 + + 8 files changed, 368 insertions(+), 147 deletions(-) + create mode 100644 libselinux/man/man3/selinux_restorecon_parallel.3 + +diff --git a/libselinux/include/selinux/restorecon.h b/libselinux/include/selinux/restorecon.h +index ca8ce768587a..8f9a030cda98 100644 +--- a/libselinux/include/selinux/restorecon.h ++++ b/libselinux/include/selinux/restorecon.h +@@ -2,6 +2,7 @@ + #define _RESTORECON_H_ + + #include ++#include + #include + + #ifdef __cplusplus +@@ -23,6 +24,19 @@ extern "C" { + */ + extern int selinux_restorecon(const char *pathname, + unsigned int restorecon_flags); ++/** ++ * selinux_restorecon_parallel - Relabel files, optionally use more threads. ++ * @pathname: specifies file/directory to relabel. ++ * @restorecon_flags: specifies the actions to be performed when relabeling. ++ * @nthreads: specifies the number of threads to use (0 = use number of CPUs ++ * currently online) ++ * ++ * Same as selinux_restorecon(3), but allows to use multiple threads to do ++ * the work. ++ */ ++extern int selinux_restorecon_parallel(const char *pathname, ++ unsigned int restorecon_flags, ++ size_t nthreads); + /* + * restorecon_flags options + */ +diff --git a/libselinux/man/man3/selinux_restorecon.3 b/libselinux/man/man3/selinux_restorecon.3 +index c4576fe79ff6..500845917fb8 100644 +--- a/libselinux/man/man3/selinux_restorecon.3 ++++ b/libselinux/man/man3/selinux_restorecon.3 +@@ -11,6 +11,14 @@ selinux_restorecon \- restore file(s) default SELinux security contexts + .br + .BI "unsigned int " restorecon_flags ");" + .in ++.sp ++.BI "int selinux_restorecon_parallel(const char *" pathname , ++.in +\w'int selinux_restorecon_parallel('u ++.br ++.BI "unsigned int " restorecon_flags "," ++.br ++.BI "size_t " nthreads ");" ++.in + . + .SH "DESCRIPTION" + .BR selinux_restorecon () +@@ -187,6 +195,27 @@ unless the + .B SELINUX_RESTORECON_IGNORE_MOUNTS + flag has been set. + .RE ++.sp ++.BR selinux_restorecon_parallel() ++is similar to ++.BR selinux_restorecon (3), ++but accepts another parameter that allows to run relabeling over multiple ++threads: ++.sp ++.RS ++.IR nthreads ++specifies the number of threads to use during relabeling. When set to 1, ++the behavior is the same as calling ++.BR selinux_restorecon (3). ++When set to 0, the function will try to use as many threads as there are ++online CPU cores. When set to any other number, the function will try to use ++the given number of threads. ++.sp ++Note that to use the parallel relabeling capability, the calling process ++must be linked with the ++.B libpthread ++library (either at compile time or dynamically at run time). Otherwise the ++function will print a warning and fall back to the single threaded mode. + . + .SH "RETURN VALUE" + On success, zero is returned. On error, \-1 is returned and +diff --git a/libselinux/man/man3/selinux_restorecon_parallel.3 b/libselinux/man/man3/selinux_restorecon_parallel.3 +new file mode 100644 +index 000000000000..092d8412cc93 +--- /dev/null ++++ b/libselinux/man/man3/selinux_restorecon_parallel.3 +@@ -0,0 +1 @@ ++.so man3/selinux_restorecon.3 +diff --git a/libselinux/src/libselinux.map b/libselinux/src/libselinux.map +index 2a368e93f9fd..d138e951ef0d 100644 +--- a/libselinux/src/libselinux.map ++++ b/libselinux/src/libselinux.map +@@ -240,3 +240,8 @@ LIBSELINUX_1.0 { + local: + *; + }; ++ ++LIBSELINUX_3.3 { ++ global: ++ selinux_restorecon_parallel; ++} LIBSELINUX_1.0; +diff --git a/libselinux/src/selinux_internal.h b/libselinux/src/selinux_internal.h +index 27e9ac532c3f..297dcf26dee3 100644 +--- a/libselinux/src/selinux_internal.h ++++ b/libselinux/src/selinux_internal.h +@@ -69,6 +69,22 @@ extern int selinux_page_size ; + pthread_mutex_unlock(LOCK); \ + } while (0) + ++#pragma weak pthread_create ++#pragma weak pthread_join ++#pragma weak pthread_cond_init ++#pragma weak pthread_cond_signal ++#pragma weak pthread_cond_destroy ++#pragma weak pthread_cond_wait ++ ++/* check if all functions needed to do parallel operations are available */ ++#define __pthread_supported ( \ ++ pthread_create && \ ++ pthread_join && \ ++ pthread_cond_init && \ ++ pthread_cond_destroy && \ ++ pthread_cond_signal && \ ++ pthread_cond_wait \ ++) + + #define SELINUXDIR "/etc/selinux/" + #define SELINUXCONFIG SELINUXDIR "config" +diff --git a/libselinux/src/selinux_restorecon.c b/libselinux/src/selinux_restorecon.c +index 169dfe3ae232..f7e84657d09d 100644 +--- a/libselinux/src/selinux_restorecon.c ++++ b/libselinux/src/selinux_restorecon.c +@@ -610,7 +610,7 @@ out: + } + + static int restorecon_sb(const char *pathname, const struct stat *sb, +- struct rest_flags *flags) ++ struct rest_flags *flags, bool first) + { + char *newcon = NULL; + char *curcon = NULL; +@@ -639,7 +639,7 @@ static int restorecon_sb(const char *pathname, const struct stat *sb, + sb->st_mode); + + if (rc < 0) { +- if (errno == ENOENT && flags->warnonnomatch) ++ if (errno == ENOENT && flags->warnonnomatch && first) + selinux_log(SELINUX_INFO, + "Warning no default label for %s\n", + lookup_path); +@@ -814,66 +814,215 @@ oom: + goto free; + } + ++struct rest_state { ++ struct rest_flags flags; ++ dev_t dev_num; ++ struct statfs sfsb; ++ bool ignore_digest; ++ bool setrestorecondigest; ++ bool parallel; + +-/* +- * Public API +- */ ++ FTS *fts; ++ FTSENT *ftsent_first; ++ struct dir_hash_node *head, *current; ++ bool abort; ++ int error; ++ int saved_errno; ++ pthread_mutex_t mutex; ++}; + +-/* selinux_restorecon(3) - Main function that is responsible for labeling */ +-int selinux_restorecon(const char *pathname_orig, +- unsigned int restorecon_flags) ++static void *selinux_restorecon_thread(void *arg) + { +- struct rest_flags flags; ++ struct rest_state *state = arg; ++ FTS *fts = state->fts; ++ FTSENT *ftsent; ++ int error; ++ char ent_path[PATH_MAX]; ++ struct stat ent_st; ++ bool first = false; ++ ++ if (state->parallel) ++ pthread_mutex_lock(&state->mutex); ++ ++ if (state->ftsent_first) { ++ ftsent = state->ftsent_first; ++ state->ftsent_first = NULL; ++ first = true; ++ goto loop_body; ++ } ++ ++ while (((void)(errno = 0), ftsent = fts_read(fts)) != NULL) { ++loop_body: ++ /* If the FTS_XDEV flag is set and the device is different */ ++ if (state->flags.set_xdev && ++ ftsent->fts_statp->st_dev != state->dev_num) ++ continue; ++ ++ switch (ftsent->fts_info) { ++ case FTS_DC: ++ selinux_log(SELINUX_ERROR, ++ "Directory cycle on %s.\n", ++ ftsent->fts_path); ++ errno = ELOOP; ++ state->error = -1; ++ state->abort = true; ++ goto finish; ++ case FTS_DP: ++ continue; ++ case FTS_DNR: ++ error = errno; ++ errno = ftsent->fts_errno; ++ selinux_log(SELINUX_ERROR, ++ "Could not read %s: %m.\n", ++ ftsent->fts_path); ++ errno = error; ++ fts_set(fts, ftsent, FTS_SKIP); ++ continue; ++ case FTS_NS: ++ error = errno; ++ errno = ftsent->fts_errno; ++ selinux_log(SELINUX_ERROR, ++ "Could not stat %s: %m.\n", ++ ftsent->fts_path); ++ errno = error; ++ fts_set(fts, ftsent, FTS_SKIP); ++ continue; ++ case FTS_ERR: ++ error = errno; ++ errno = ftsent->fts_errno; ++ selinux_log(SELINUX_ERROR, ++ "Error on %s: %m.\n", ++ ftsent->fts_path); ++ errno = error; ++ fts_set(fts, ftsent, FTS_SKIP); ++ continue; ++ case FTS_D: ++ if (state->sfsb.f_type == SYSFS_MAGIC && ++ !selabel_partial_match(fc_sehandle, ++ ftsent->fts_path)) { ++ fts_set(fts, ftsent, FTS_SKIP); ++ continue; ++ } ++ ++ if (check_excluded(ftsent->fts_path)) { ++ fts_set(fts, ftsent, FTS_SKIP); ++ continue; ++ } + +- flags.nochange = (restorecon_flags & ++ if (state->setrestorecondigest) { ++ struct dir_hash_node *new_node = NULL; ++ ++ if (check_context_match_for_dir(ftsent->fts_path, ++ &new_node, ++ state->error) && ++ !state->ignore_digest) { ++ selinux_log(SELINUX_INFO, ++ "Skipping restorecon on directory(%s)\n", ++ ftsent->fts_path); ++ fts_set(fts, ftsent, FTS_SKIP); ++ continue; ++ } ++ ++ if (new_node && !state->error) { ++ if (!state->current) { ++ state->current = new_node; ++ state->head = state->current; ++ } else { ++ state->current->next = new_node; ++ state->current = new_node; ++ } ++ } ++ } ++ /* fall through */ ++ default: ++ strcpy(ent_path, ftsent->fts_path); ++ ent_st = *ftsent->fts_statp; ++ if (state->parallel) ++ pthread_mutex_unlock(&state->mutex); ++ ++ error = restorecon_sb(ent_path, &ent_st, &state->flags, ++ first); ++ ++ if (state->parallel) { ++ pthread_mutex_lock(&state->mutex); ++ if (state->abort) ++ goto unlock; ++ } ++ ++ state->error |= error; ++ first = false; ++ if (error && state->flags.abort_on_error) { ++ state->abort = true; ++ goto finish; ++ } ++ break; ++ } ++ } ++ ++finish: ++ if (!state->saved_errno) ++ state->saved_errno = errno; ++unlock: ++ if (state->parallel) ++ pthread_mutex_unlock(&state->mutex); ++ return NULL; ++} ++ ++static int selinux_restorecon_common(const char *pathname_orig, ++ unsigned int restorecon_flags, ++ size_t nthreads) ++{ ++ struct rest_state state; ++ ++ state.flags.nochange = (restorecon_flags & + SELINUX_RESTORECON_NOCHANGE) ? true : false; +- flags.verbose = (restorecon_flags & ++ state.flags.verbose = (restorecon_flags & + SELINUX_RESTORECON_VERBOSE) ? true : false; +- flags.progress = (restorecon_flags & ++ state.flags.progress = (restorecon_flags & + SELINUX_RESTORECON_PROGRESS) ? true : false; +- flags.mass_relabel = (restorecon_flags & ++ state.flags.mass_relabel = (restorecon_flags & + SELINUX_RESTORECON_MASS_RELABEL) ? true : false; +- flags.recurse = (restorecon_flags & ++ state.flags.recurse = (restorecon_flags & + SELINUX_RESTORECON_RECURSE) ? true : false; +- flags.set_specctx = (restorecon_flags & ++ state.flags.set_specctx = (restorecon_flags & + SELINUX_RESTORECON_SET_SPECFILE_CTX) ? true : false; +- flags.userealpath = (restorecon_flags & ++ state.flags.userealpath = (restorecon_flags & + SELINUX_RESTORECON_REALPATH) ? true : false; +- flags.set_xdev = (restorecon_flags & ++ state.flags.set_xdev = (restorecon_flags & + SELINUX_RESTORECON_XDEV) ? true : false; +- flags.add_assoc = (restorecon_flags & ++ state.flags.add_assoc = (restorecon_flags & + SELINUX_RESTORECON_ADD_ASSOC) ? true : false; +- flags.abort_on_error = (restorecon_flags & ++ state.flags.abort_on_error = (restorecon_flags & + SELINUX_RESTORECON_ABORT_ON_ERROR) ? true : false; +- flags.syslog_changes = (restorecon_flags & ++ state.flags.syslog_changes = (restorecon_flags & + SELINUX_RESTORECON_SYSLOG_CHANGES) ? true : false; +- flags.log_matches = (restorecon_flags & ++ state.flags.log_matches = (restorecon_flags & + SELINUX_RESTORECON_LOG_MATCHES) ? true : false; +- flags.ignore_noent = (restorecon_flags & ++ state.flags.ignore_noent = (restorecon_flags & + SELINUX_RESTORECON_IGNORE_NOENTRY) ? true : false; +- flags.warnonnomatch = true; +- flags.conflicterror = (restorecon_flags & ++ state.flags.warnonnomatch = true; ++ state.flags.conflicterror = (restorecon_flags & + SELINUX_RESTORECON_CONFLICT_ERROR) ? true : false; + ignore_mounts = (restorecon_flags & + SELINUX_RESTORECON_IGNORE_MOUNTS) ? true : false; +- bool ignore_digest = (restorecon_flags & ++ state.ignore_digest = (restorecon_flags & + SELINUX_RESTORECON_IGNORE_DIGEST) ? true : false; +- bool setrestorecondigest = true; ++ state.setrestorecondigest = true; ++ ++ state.head = NULL; ++ state.current = NULL; ++ state.abort = false; ++ state.error = 0; ++ state.saved_errno = 0; + + struct stat sb; +- struct statfs sfsb; +- FTS *fts; +- FTSENT *ftsent; + char *pathname = NULL, *pathdnamer = NULL, *pathdname, *pathbname; + char *paths[2] = { NULL, NULL }; + int fts_flags, error, sverrno; +- dev_t dev_num = 0; + struct dir_hash_node *current = NULL; +- struct dir_hash_node *head = NULL; +- int errno_tmp; + +- if (flags.verbose && flags.progress) +- flags.verbose = false; ++ if (state.flags.verbose && state.flags.progress) ++ state.flags.verbose = false; + + __selinux_once(fc_once, restorecon_init); + +@@ -886,13 +1035,31 @@ int selinux_restorecon(const char *pathname_orig, + */ + if (selabel_no_digest || + (restorecon_flags & SELINUX_RESTORECON_SKIP_DIGEST)) +- setrestorecondigest = false; ++ state.setrestorecondigest = false; ++ ++ if (!__pthread_supported) { ++ if (nthreads != 1) { ++ nthreads = 1; ++ selinux_log(SELINUX_WARNING, ++ "Threading functionality not available, falling back to 1 thread."); ++ } ++ } else if (nthreads == 0) { ++ long nproc = sysconf(_SC_NPROCESSORS_ONLN); ++ ++ if (nproc > 0) { ++ nthreads = nproc; ++ } else { ++ nthreads = 1; ++ selinux_log(SELINUX_WARNING, ++ "Unable to detect CPU count, falling back to 1 thread."); ++ } ++ } + + /* + * Convert passed-in pathname to canonical pathname by resolving + * realpath of containing dir, then appending last component name. + */ +- if (flags.userealpath) { ++ if (state.flags.userealpath) { + char *basename_cpy = strdup(pathname_orig); + if (!basename_cpy) + goto realpatherr; +@@ -937,7 +1104,7 @@ int selinux_restorecon(const char *pathname_orig, + paths[0] = pathname; + + if (lstat(pathname, &sb) < 0) { +- if (flags.ignore_noent && errno == ENOENT) { ++ if (state.flags.ignore_noent && errno == ENOENT) { + free(pathdnamer); + free(pathname); + return 0; +@@ -952,21 +1119,21 @@ int selinux_restorecon(const char *pathname_orig, + + /* Skip digest if not a directory */ + if (!S_ISDIR(sb.st_mode)) +- setrestorecondigest = false; ++ state.setrestorecondigest = false; + +- if (!flags.recurse) { ++ if (!state.flags.recurse) { + if (check_excluded(pathname)) { + error = 0; + goto cleanup; + } + +- error = restorecon_sb(pathname, &sb, &flags); ++ error = restorecon_sb(pathname, &sb, &state.flags, true); + goto cleanup; + } + + /* Obtain fs type */ +- memset(&sfsb, 0, sizeof sfsb); +- if (!S_ISLNK(sb.st_mode) && statfs(pathname, &sfsb) < 0) { ++ memset(&state.sfsb, 0, sizeof(state.sfsb)); ++ if (!S_ISLNK(sb.st_mode) && statfs(pathname, &state.sfsb) < 0) { + selinux_log(SELINUX_ERROR, + "statfs(%s) failed: %m\n", + pathname); +@@ -975,21 +1142,21 @@ int selinux_restorecon(const char *pathname_orig, + } + + /* Skip digest on in-memory filesystems and /sys */ +- if (sfsb.f_type == RAMFS_MAGIC || sfsb.f_type == TMPFS_MAGIC || +- sfsb.f_type == SYSFS_MAGIC) +- setrestorecondigest = false; ++ if (state.sfsb.f_type == RAMFS_MAGIC || state.sfsb.f_type == TMPFS_MAGIC || ++ state.sfsb.f_type == SYSFS_MAGIC) ++ state.setrestorecondigest = false; + +- if (flags.set_xdev) ++ if (state.flags.set_xdev) + fts_flags = FTS_PHYSICAL | FTS_NOCHDIR | FTS_XDEV; + else + fts_flags = FTS_PHYSICAL | FTS_NOCHDIR; + +- fts = fts_open(paths, fts_flags, NULL); +- if (!fts) ++ state.fts = fts_open(paths, fts_flags, NULL); ++ if (!state.fts) + goto fts_err; + +- ftsent = fts_read(fts); +- if (!ftsent) ++ state.ftsent_first = fts_read(state.fts); ++ if (!state.ftsent_first) + goto fts_err; + + /* +@@ -1001,106 +1168,66 @@ int selinux_restorecon(const char *pathname_orig, + * directories with a different device number when the FTS_XDEV flag + * is set (from http://marc.info/?l=selinux&m=124688830500777&w=2). + */ +- dev_num = ftsent->fts_statp->st_dev; ++ state.dev_num = state.ftsent_first->fts_statp->st_dev; + +- error = 0; +- do { +- /* If the FTS_XDEV flag is set and the device is different */ +- if (flags.set_xdev && ftsent->fts_statp->st_dev != dev_num) +- continue; ++ if (nthreads == 1) { ++ state.parallel = false; ++ selinux_restorecon_thread(&state); ++ } else { ++ size_t i; ++ pthread_t self = pthread_self(); ++ pthread_t *threads = NULL; + +- switch (ftsent->fts_info) { +- case FTS_DC: +- selinux_log(SELINUX_ERROR, +- "Directory cycle on %s.\n", +- ftsent->fts_path); +- errno = ELOOP; +- error = -1; +- goto out; +- case FTS_DP: +- continue; +- case FTS_DNR: +- errno_tmp = errno; +- errno = ftsent->fts_errno; +- selinux_log(SELINUX_ERROR, +- "Could not read %s: %m.\n", +- ftsent->fts_path); +- errno = errno_tmp; +- fts_set(fts, ftsent, FTS_SKIP); +- continue; +- case FTS_NS: +- errno_tmp = errno; +- errno = ftsent->fts_errno; +- selinux_log(SELINUX_ERROR, +- "Could not stat %s: %m.\n", +- ftsent->fts_path); +- errno = errno_tmp; +- fts_set(fts, ftsent, FTS_SKIP); +- continue; +- case FTS_ERR: +- errno_tmp = errno; +- errno = ftsent->fts_errno; +- selinux_log(SELINUX_ERROR, +- "Error on %s: %m.\n", +- ftsent->fts_path); +- errno = errno_tmp; +- fts_set(fts, ftsent, FTS_SKIP); +- continue; +- case FTS_D: +- if (sfsb.f_type == SYSFS_MAGIC && +- !selabel_partial_match(fc_sehandle, +- ftsent->fts_path)) { +- fts_set(fts, ftsent, FTS_SKIP); +- continue; +- } ++ pthread_mutex_init(&state.mutex, NULL); + +- if (check_excluded(ftsent->fts_path)) { +- fts_set(fts, ftsent, FTS_SKIP); +- continue; ++ threads = calloc(nthreads - 1, sizeof(*threads)); ++ if (!threads) ++ goto oom; ++ ++ state.parallel = true; ++ /* ++ * Start (nthreads - 1) threads - the main thread is going to ++ * take part, too. ++ */ ++ for (i = 0; i < nthreads - 1; i++) { ++ if (pthread_create(&threads[i], NULL, ++ selinux_restorecon_thread, &state)) { ++ /* ++ * If any thread fails to be created, just mark ++ * it as such and let the successfully created ++ * threads do the job. In the worst case the ++ * main thread will do everything, but that's ++ * still better than to give up. ++ */ ++ threads[i] = self; + } ++ } + +- if (setrestorecondigest) { +- struct dir_hash_node *new_node = NULL; ++ /* Let's join in on the fun! */ ++ selinux_restorecon_thread(&state); + +- if (check_context_match_for_dir(ftsent->fts_path, +- &new_node, +- error) && +- !ignore_digest) { +- selinux_log(SELINUX_INFO, +- "Skipping restorecon on directory(%s)\n", +- ftsent->fts_path); +- fts_set(fts, ftsent, FTS_SKIP); +- continue; +- } +- +- if (new_node && !error) { +- if (!current) { +- current = new_node; +- head = current; +- } else { +- current->next = new_node; +- current = current->next; +- } +- } +- } +- /* fall through */ +- default: +- error |= restorecon_sb(ftsent->fts_path, +- ftsent->fts_statp, &flags); +- if (flags.warnonnomatch) +- flags.warnonnomatch = false; +- if (error && flags.abort_on_error) +- goto out; +- break; ++ /* Now wait for all threads to finish. */ ++ for (i = 0; i < nthreads - 1; i++) { ++ /* Skip threads that failed to be created. */ ++ if (pthread_equal(threads[i], self)) ++ continue; ++ pthread_join(threads[i], NULL); + } +- } while ((ftsent = fts_read(fts)) != NULL); ++ free(threads); ++ ++ pthread_mutex_destroy(&state.mutex); ++ } ++ ++ error = state.error; ++ if (state.saved_errno) ++ goto out; + + /* + * Labeling successful. Write partial match digests for subdirectories. + * TODO: Write digest upon FTS_DP if no error occurs in its descents. + */ +- if (setrestorecondigest && !flags.nochange && !error) { +- current = head; ++ if (state.setrestorecondigest && !state.flags.nochange && !error) { ++ current = state.head; + while (current != NULL) { + if (setxattr(current->path, + RESTORECON_PARTIAL_MATCH_DIGEST, +@@ -1115,22 +1242,21 @@ int selinux_restorecon(const char *pathname_orig, + } + + out: +- if (flags.progress && flags.mass_relabel) ++ if (state.flags.progress && state.flags.mass_relabel) + fprintf(stdout, "\r%s 100.0%%\n", pathname); + +- sverrno = errno; +- (void) fts_close(fts); +- errno = sverrno; ++ (void) fts_close(state.fts); ++ errno = state.saved_errno; + cleanup: +- if (flags.add_assoc) { +- if (flags.verbose) ++ if (state.flags.add_assoc) { ++ if (state.flags.verbose) + filespec_eval(); + filespec_destroy(); + } + free(pathdnamer); + free(pathname); + +- current = head; ++ current = state.head; + while (current != NULL) { + struct dir_hash_node *next = current->next; + +@@ -1164,6 +1290,26 @@ fts_err: + goto cleanup; + } + ++ ++/* ++ * Public API ++ */ ++ ++/* selinux_restorecon(3) - Main function that is responsible for labeling */ ++int selinux_restorecon(const char *pathname_orig, ++ unsigned int restorecon_flags) ++{ ++ return selinux_restorecon_common(pathname_orig, restorecon_flags, 1); ++} ++ ++/* selinux_restorecon_parallel(3) - Parallel version of selinux_restorecon(3) */ ++int selinux_restorecon_parallel(const char *pathname_orig, ++ unsigned int restorecon_flags, ++ size_t nthreads) ++{ ++ return selinux_restorecon_common(pathname_orig, restorecon_flags, nthreads); ++} ++ + /* selinux_restorecon_set_sehandle(3) is called to set the global fc handle */ + void selinux_restorecon_set_sehandle(struct selabel_handle *hndl) + { +diff --git a/libselinux/src/selinuxswig_python.i b/libselinux/src/selinuxswig_python.i +index 4c73bf92df96..17e03b9e36a5 100644 +--- a/libselinux/src/selinuxswig_python.i ++++ b/libselinux/src/selinuxswig_python.i +@@ -20,7 +20,7 @@ DISABLED = -1 + PERMISSIVE = 0 + ENFORCING = 1 + +-def restorecon(path, recursive=False, verbose=False, force=False): ++def restorecon(path, recursive=False, verbose=False, force=False, nthreads=1): + """ Restore SELinux context on a given path + + Arguments: +@@ -32,6 +32,8 @@ def restorecon(path, recursive=False, verbose=False, force=False): + force -- Force reset of context to match file_context for customizable files, + and the default file context, changing the user, role, range portion as well + as the type (default False) ++ nthreads -- The number of threads to use during relabeling, or 0 to use as many ++ threads as there are online CPU cores (default 1) + """ + + restorecon_flags = SELINUX_RESTORECON_IGNORE_DIGEST | SELINUX_RESTORECON_REALPATH +@@ -41,7 +43,7 @@ def restorecon(path, recursive=False, verbose=False, force=False): + restorecon_flags |= SELINUX_RESTORECON_VERBOSE + if force: + restorecon_flags |= SELINUX_RESTORECON_SET_SPECFILE_CTX +- selinux_restorecon(os.path.expanduser(path), restorecon_flags) ++ selinux_restorecon_parallel(os.path.expanduser(path), restorecon_flags, nthreads) + + def chcon(path, context, recursive=False): + """ Set the SELinux context on a given path """ +diff --git a/libselinux/src/selinuxswig_python_exception.i b/libselinux/src/selinuxswig_python_exception.i +index 237ea69ad5f5..a02f4923a1e7 100644 +--- a/libselinux/src/selinuxswig_python_exception.i ++++ b/libselinux/src/selinuxswig_python_exception.i +@@ -1183,6 +1183,14 @@ + } + } + ++%exception selinux_restorecon_parallel { ++ $action ++ if (result < 0) { ++ PyErr_SetFromErrno(PyExc_OSError); ++ SWIG_fail; ++ } ++} ++ + %exception selinux_restorecon_set_alt_rootpath { + $action + if (result < 0) { +-- +2.33.1 + diff --git a/0009-libselinux-Fix-selinux_restorecon_parallel-symbol-ve.patch b/0009-libselinux-Fix-selinux_restorecon_parallel-symbol-ve.patch new file mode 100644 index 0000000..a8ce120 --- /dev/null +++ b/0009-libselinux-Fix-selinux_restorecon_parallel-symbol-ve.patch @@ -0,0 +1,29 @@ +From 9456297275987dedefe2e8ad508360be9d9f9e7f Mon Sep 17 00:00:00 2001 +From: Petr Lautrbach +Date: Tue, 23 Nov 2021 11:31:08 +0100 +Subject: [PATCH] libselinux: Fix selinux_restorecon_parallel symbol version + +selinux_restorecon_parallel was originally proposed before 3.3, but it +was merged after release so it will be introduced in version 3.4. + +Signed-off-by: Petr Lautrbach +--- + libselinux/src/libselinux.map | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/libselinux/src/libselinux.map b/libselinux/src/libselinux.map +index d138e951ef0d..4acf1caacb55 100644 +--- a/libselinux/src/libselinux.map ++++ b/libselinux/src/libselinux.map +@@ -241,7 +241,7 @@ LIBSELINUX_1.0 { + *; + }; + +-LIBSELINUX_3.3 { ++LIBSELINUX_3.4 { + global: + selinux_restorecon_parallel; + } LIBSELINUX_1.0; +-- +2.33.1 + diff --git a/libselinux.spec b/libselinux.spec index 0d93511..6b62095 100644 --- a/libselinux.spec +++ b/libselinux.spec @@ -17,6 +17,14 @@ Url: https://github.com/SELinuxProject/selinux/wiki # $ i=1; for j in 00*patch; do printf "Patch%04d: %s\n" $i $j; i=$((i+1));done # Patch list start Patch0001: 0001-Use-SHA-2-instead-of-SHA-1.patch +Patch0002: 0002-label_file-fix-a-data-race.patch +Patch0003: 0003-selinux_restorecon-simplify-fl_head-allocation-by-us.patch +Patch0004: 0004-selinux_restorecon-protect-file_spec-list-with-a-mut.patch +Patch0005: 0005-libselinux-make-selinux_log-thread-safe.patch +Patch0006: 0006-libselinux-make-is_context_customizable-thread-safe.patch +Patch0007: 0007-selinux_restorecon-add-a-global-mutex-to-synchronize.patch +Patch0008: 0008-selinux_restorecon-introduce-selinux_restorecon_para.patch +Patch0009: 0009-libselinux-Fix-selinux_restorecon_parallel-symbol-ve.patch # Patch list end BuildRequires: gcc make BuildRequires: ruby-devel ruby libsepol-static >= %{libsepolver} swig pcre2-devel xz-devel