From ef4edd927acca9beb86b85395878f20e593f5145 Mon Sep 17 00:00:00 2001 From: Petr Lautrbach Date: Mon, 25 May 2026 09:33:18 +0200 Subject: [PATCH] policycoreutils-3.10-3 - Several sandbox and seunshare security improvements Resolves: RHEL-175829 --- ...fallible-function-calls-by-checking-.patch | 63 ++ ...-seunshare-pass-O_NOFOLLOW-to-openat.patch | 30 + ...e-switch-seunshare_mount_file-to-use.patch | 52 ++ ...hare-fix-error-checking-for-setfsuid.patch | 33 + ...e-remount-tmp-and-var-tmp-with-the-p.patch | 86 +++ ...e-prevent-rsync-from-interpreting-pa.patch | 75 +++ 0015-sandbox-seunshare-fix-getopt-flags.patch | 44 ++ ...share-prevent-path-traversal-via-W-P.patch | 47 ++ ...nshare-verify-RUNTIME_DIR-before-use.patch | 38 ++ ...x-seunshare-drop-unused-runuserdir_r.patch | 48 ++ ...e-fix-killall-realloc-and-missing-ty.patch | 95 +++ ...e-rewrite-to-pin-directories-before-.patch | 606 ++++++++++++++++++ ...seunshare-fully-check-setfsuid-calls.patch | 158 +++++ ...e-check-owner-in-seunshare_mount_fil.patch | 85 +++ ...dbox-seunshare-fix-fd_tmpdir_r-check.patch | 29 + ...nd-Do-not-unlink-pidfile-if-not-used.patch | 30 + changelog | 3 + policycoreutils.spec | 18 +- 18 files changed, 1539 insertions(+), 1 deletion(-) create mode 100644 0009-seunshare-guard-fallible-function-calls-by-checking-.patch create mode 100644 0010-sandbox-seunshare-pass-O_NOFOLLOW-to-openat.patch create mode 100644 0011-sandbox-seunshare-switch-seunshare_mount_file-to-use.patch create mode 100644 0012-sandbox-seunshare-fix-error-checking-for-setfsuid.patch create mode 100644 0013-sandbox-seunshare-remount-tmp-and-var-tmp-with-the-p.patch create mode 100644 0014-sandbox-seunshare-prevent-rsync-from-interpreting-pa.patch create mode 100644 0015-sandbox-seunshare-fix-getopt-flags.patch create mode 100644 0016-sandbox-seunshare-prevent-path-traversal-via-W-P.patch create mode 100644 0017-sandbox-seunshare-verify-RUNTIME_DIR-before-use.patch create mode 100644 0018-sandbox-seunshare-drop-unused-runuserdir_r.patch create mode 100644 0019-sandbox-seunshare-fix-killall-realloc-and-missing-ty.patch create mode 100644 0020-sandbox-seunshare-rewrite-to-pin-directories-before-.patch create mode 100644 0021-sandbox-seunshare-fully-check-setfsuid-calls.patch create mode 100644 0022-sandbox-seunshare-check-owner-in-seunshare_mount_fil.patch create mode 100644 0023-sandbox-seunshare-fix-fd_tmpdir_r-check.patch create mode 100644 0024-restorecond-Do-not-unlink-pidfile-if-not-used.patch diff --git a/0009-seunshare-guard-fallible-function-calls-by-checking-.patch b/0009-seunshare-guard-fallible-function-calls-by-checking-.patch new file mode 100644 index 0000000..028d2bd --- /dev/null +++ b/0009-seunshare-guard-fallible-function-calls-by-checking-.patch @@ -0,0 +1,63 @@ +From 0edfc7cd42fdded9c697370d6932eecad2aa7449 Mon Sep 17 00:00:00 2001 +From: Rahul Sandhu +Date: Tue, 14 Apr 2026 18:40:00 +0000 +Subject: [PATCH] seunshare: guard fallible function calls by checking retval +Content-type: text/plain + +seunshare currently segfaults is passed --kill and an invalid context +via -Z. + +Signed-off-by: Rahul Sandhu +Acked-by: Stephen Smalley +--- + sandbox/seunshare.c | 26 +++++++++++++++++++------- + 1 file changed, 19 insertions(+), 7 deletions(-) + +diff --git a/sandbox/seunshare.c b/sandbox/seunshare.c +index 2512a18c7b52..e89a9fecf9f6 100644 +--- a/sandbox/seunshare.c ++++ b/sandbox/seunshare.c +@@ -679,9 +679,19 @@ killall (const char *execcon) + return -1; + } + pids = 0; +- context_t con; +- con = context_new(execcon); +- const char *mcs = context_range_get(con); ++ context_t con = context_new(execcon); ++ if (!con) { ++ free(pid_table); ++ (void)closedir(dir); ++ return -1; ++ } ++ const char *const mcs = context_range_get(con); ++ if (!mcs) { ++ context_free(con); ++ free(pid_table); ++ (void)closedir(dir); ++ return -1; ++ } + printf("mcs=%s\n", mcs); + while ((de = readdir (dir)) != NULL) { + if (!(pid = (pid_t)atoi(de->d_name)) || pid == self) +@@ -708,11 +718,13 @@ killall (const char *execcon) + if (getpidcon(id, &scon) == 0) { + + context_t pidcon = context_new(scon); +- /* Attempt to kill remaining processes */ +- if (strcmp(context_range_get(pidcon), mcs) == 0) +- kill(id, SIGKILL); ++ if (pidcon) { ++ /* Attempt to kill remaining processes */ ++ if (strcmp(context_range_get(pidcon), mcs) == 0) ++ kill(id, SIGKILL); + +- context_free(pidcon); ++ context_free(pidcon); ++ } + freecon(scon); + } + running++; +-- +2.54.0 + diff --git a/0010-sandbox-seunshare-pass-O_NOFOLLOW-to-openat.patch b/0010-sandbox-seunshare-pass-O_NOFOLLOW-to-openat.patch new file mode 100644 index 0000000..2191dec --- /dev/null +++ b/0010-sandbox-seunshare-pass-O_NOFOLLOW-to-openat.patch @@ -0,0 +1,30 @@ +From 8d2c7360f5749b3ccacd348c495399f24318d7b4 Mon Sep 17 00:00:00 2001 +From: Stephen Smalley +Date: Tue, 12 May 2026 14:34:02 -0400 +Subject: [PATCH] sandbox/seunshare: pass O_NOFOLLOW to openat() +Content-type: text/plain + +We do not want to follow symlinks here. + +Acked-by: James Carter +Signed-off-by: Stephen Smalley +--- + sandbox/seunshare.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/sandbox/seunshare.c b/sandbox/seunshare.c +index e89a9fecf9f6..a1900eaa7600 100644 +--- a/sandbox/seunshare.c ++++ b/sandbox/seunshare.c +@@ -428,7 +428,7 @@ static bool rm_rf(int targetfd, const char *path) { + } + + if (S_ISDIR(statbuf.st_mode)) { +- const int newfd = openat(targetfd, path, O_RDONLY | O_DIRECTORY | O_CLOEXEC); ++ const int newfd = openat(targetfd, path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); + if (newfd < 0) { + perror("openat"); + return false; +-- +2.54.0 + diff --git a/0011-sandbox-seunshare-switch-seunshare_mount_file-to-use.patch b/0011-sandbox-seunshare-switch-seunshare_mount_file-to-use.patch new file mode 100644 index 0000000..b437195 --- /dev/null +++ b/0011-sandbox-seunshare-switch-seunshare_mount_file-to-use.patch @@ -0,0 +1,52 @@ +From e964a574c2ca8ec1bcd3172dc185ada8044dceee Mon Sep 17 00:00:00 2001 +From: Stephen Smalley +Date: Tue, 12 May 2026 15:26:31 -0400 +Subject: [PATCH] sandbox/seunshare: switch seunshare_mount_file() to use + open() +Content-type: text/plain + +seunshare_mount_file() currently uses fopen() to create the dst +if it doesn't already exist. Switch to using open() with +explicitly specified flags including O_NOFOLLOW and an explicitly +specified mode for the new file. + +Signed-off-by: Stephen Smalley +Acked-by: Petr Lautrbach +--- + sandbox/seunshare.c | 14 ++++++++------ + 1 file changed, 8 insertions(+), 6 deletions(-) + +diff --git a/sandbox/seunshare.c b/sandbox/seunshare.c +index a1900eaa7600..17a727e78b5f 100644 +--- a/sandbox/seunshare.c ++++ b/sandbox/seunshare.c +@@ -304,18 +304,20 @@ static int seunshare_mount(const char *src, const char *dst, struct stat *src_st + */ + static int seunshare_mount_file(const char *src, const char *dst) + { +- int flags = 0; +- + if (verbose) + printf(_("Mounting %s on %s\n"), src, dst); + + if (access(dst, F_OK) == -1) { +- FILE *fptr; +- fptr = fopen(dst, "w"); +- fclose(fptr); ++ int fd = open(dst, O_WRONLY | O_CREAT | O_NOFOLLOW | O_CLOEXEC, 0600); ++ if (fd < 0) { ++ fprintf(stderr, _("Failed to create mount point %s: %m\n"), dst); ++ return -1; ++ } ++ close(fd); + } ++ + /* mount file */ +- if (mount(src, dst, NULL, MS_BIND | flags, NULL) < 0) { ++ if (mount(src, dst, NULL, MS_BIND, NULL) < 0) { + fprintf(stderr, _("Failed to mount %s on %s: %s\n"), src, dst, strerror(errno)); + return -1; + } +-- +2.54.0 + diff --git a/0012-sandbox-seunshare-fix-error-checking-for-setfsuid.patch b/0012-sandbox-seunshare-fix-error-checking-for-setfsuid.patch new file mode 100644 index 0000000..02d93e0 --- /dev/null +++ b/0012-sandbox-seunshare-fix-error-checking-for-setfsuid.patch @@ -0,0 +1,33 @@ +From 9c5320c6d4a1eb91b57e4e12737882ef131c2faa Mon Sep 17 00:00:00 2001 +From: Stephen Smalley +Date: Tue, 12 May 2026 15:35:53 -0400 +Subject: [PATCH] sandbox/seunshare: fix error checking for setfsuid() +Content-type: text/plain + +setfsuid() doesn't reliably set errno or return anything indicating +an error. + +Signed-off-by: Stephen Smalley +Acked-by: Petr Lautrbach +--- + sandbox/seunshare.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/sandbox/seunshare.c b/sandbox/seunshare.c +index 17a727e78b5f..b9c85bf20f85 100644 +--- a/sandbox/seunshare.c ++++ b/sandbox/seunshare.c +@@ -858,8 +858,8 @@ int main(int argc, char **argv) { + /* Changing fsuid is usually required when user-specified directory is + * on an NFS mount. It's also desired to avoid leaking info about + * existence of the files not accessible to the user. */ +- if (((uid_t)setfsuid(uid) != 0) && (errno != 0)) { +- fprintf(stderr, _("Error: unable to setfsuid %m\n")); ++ if ((uid_t)setfsuid(uid) != 0) { ++ fprintf(stderr, _("Error: unable to setfsuid\n")); + + return -1; + } +-- +2.54.0 + diff --git a/0013-sandbox-seunshare-remount-tmp-and-var-tmp-with-the-p.patch b/0013-sandbox-seunshare-remount-tmp-and-var-tmp-with-the-p.patch new file mode 100644 index 0000000..ade7f30 --- /dev/null +++ b/0013-sandbox-seunshare-remount-tmp-and-var-tmp-with-the-p.patch @@ -0,0 +1,86 @@ +From f78708ea5d6d3d6da4fa20527b459fa55075d8a6 Mon Sep 17 00:00:00 2001 +From: Stephen Smalley +Date: Tue, 12 May 2026 16:06:05 -0400 +Subject: [PATCH] sandbox/seunshare: remount /tmp and /var/tmp with the proper + flags +Content-type: text/plain + +mount(2) with MS_BIND ignores any nosuid/nodev/noexec flags, so +seunshare_mount() was never setting those on the /tmp and +/var/tmp mounts. Fix seunshare_mount() to remount them +with those flags after the bind mount, which does +set them properly. + +Test: +mkdir tmp +seunshare -t tmp /bin/bash +cp /bin/bash /tmp +/tmp/bash + +Signed-off-by: Stephen Smalley +Acked-by: Petr Lautrbach +--- + sandbox/seunshare.c | 21 ++++++++++++++++----- + 1 file changed, 16 insertions(+), 5 deletions(-) + +diff --git a/sandbox/seunshare.c b/sandbox/seunshare.c +index b9c85bf20f85..985e0cfba199 100644 +--- a/sandbox/seunshare.c ++++ b/sandbox/seunshare.c +@@ -260,26 +260,32 @@ static int verify_shell(const char *shell_name) + */ + static int seunshare_mount(const char *src, const char *dst, struct stat *src_st) + { +- int flags = 0; ++ int bind_flags = MS_BIND; ++ int sec_flags = 0; + int is_tmp = 0; + + if (verbose) + printf(_("Mounting %s on %s\n"), src, dst); + + if (strcmp("/tmp", dst) == 0) { +- flags = flags | MS_NODEV | MS_NOSUID | MS_NOEXEC; ++ sec_flags = MS_NODEV | MS_NOSUID | MS_NOEXEC; + is_tmp = 1; + } + + if (strncmp("/run/user", dst, 9) == 0) { +- flags = flags | MS_REC; ++ bind_flags |= MS_REC; + } + + /* mount directory */ +- if (mount(src, dst, NULL, MS_BIND | flags, NULL) < 0) { ++ if (mount(src, dst, NULL, bind_flags, NULL) < 0) { + fprintf(stderr, _("Failed to mount %s on %s: %s\n"), src, dst, strerror(errno)); + return -1; + } ++ /* remount with security flags, ignored on original bind mount */ ++ if (sec_flags && mount(NULL, dst, NULL, MS_BIND | MS_REMOUNT | sec_flags, NULL) < 0) { ++ fprintf(stderr, _("Failed to remount %s: %m\n"), dst); ++ return -1; ++ } + + /* verify whether we mounted what we expected to mount */ + if (verify_directory(dst, src_st, NULL) < 0) return -1; +@@ -289,10 +295,15 @@ static int seunshare_mount(const char *src, const char *dst, struct stat *src_st + if (verbose) + printf(_("Mounting /tmp on /var/tmp\n")); + +- if (mount("/tmp", "/var/tmp", NULL, MS_BIND | flags, NULL) < 0) { ++ if (mount("/tmp", "/var/tmp", NULL, MS_BIND, NULL) < 0) { + fprintf(stderr, _("Failed to mount /tmp on /var/tmp: %s\n"), strerror(errno)); + return -1; + } ++ /* remount with security flags, ignored on original bind mount */ ++ if (mount(NULL, "/var/tmp", NULL, MS_BIND | MS_REMOUNT | sec_flags, NULL) < 0) { ++ fprintf(stderr, _("Failed to remount /var/tmp: %m\n")); ++ return -1; ++ } + } + + return 0; +-- +2.54.0 + diff --git a/0014-sandbox-seunshare-prevent-rsync-from-interpreting-pa.patch b/0014-sandbox-seunshare-prevent-rsync-from-interpreting-pa.patch new file mode 100644 index 0000000..cffb736 --- /dev/null +++ b/0014-sandbox-seunshare-prevent-rsync-from-interpreting-pa.patch @@ -0,0 +1,75 @@ +From dd620e8af44e1cb59d70837e80a5d55a8d3e789f Mon Sep 17 00:00:00 2001 +From: Stephen Smalley +Date: Wed, 13 May 2026 09:47:50 -0400 +Subject: [PATCH] sandbox/seunshare: prevent rsync from interpreting paths as + options +Content-type: text/plain + +Insert a "--" after all legitimate options before any glob or +pathnames to prevent rsync from interpreting them as options. + +Signed-off-by: Stephen Smalley +--- + sandbox/seunshare.c | 16 +++++++++------- + 1 file changed, 9 insertions(+), 7 deletions(-) + +diff --git a/sandbox/seunshare.c b/sandbox/seunshare.c +index 985e0cfba199..a48c88036088 100644 +--- a/sandbox/seunshare.c ++++ b/sandbox/seunshare.c +@@ -391,8 +391,8 @@ static int rsynccmd(const char * src, const char *dst, char ***cmd) { + + free(buf); buf = NULL; + +- /* rsync -trlHDq + + dst + NULL */ +- *cmd = calloc(2 + fglob.gl_pathc + 2, sizeof(char *)); ++ /* rsync -trlHDq -- dst NULL */ ++ *cmd = calloc(3 + fglob.gl_pathc + 2, sizeof(char *)); + if (! *cmd) { + fprintf(stderr, _("Out of memory\n")); + return -1; +@@ -401,8 +401,9 @@ static int rsynccmd(const char * src, const char *dst, char ***cmd) { + args = *cmd; + strdup_or_err(args, 0, "/usr/bin/rsync"); + strdup_or_err(args, 1, "-trlHDq"); ++ strdup_or_err(args, 2, "--"); + +- for ( i=0, index = 2; i < fglob.gl_pathc; i++) { ++ for ( i=0, index = 3; i < fglob.gl_pathc; i++) { + const char *path = fglob.gl_pathv[i]; + if (bad_path(path)) continue; + strdup_or_err(args, index, path); +@@ -495,7 +496,7 @@ static int cleanup_tmpdir(const char *tmpdir, const char *src, + + /* rsync files back */ + if (copy_content) { +- args = calloc(7, sizeof(char *)); ++ args = calloc(8, sizeof(char *)); + if (! args) { + fprintf(stderr, _("Out of memory\n")); + return 1; +@@ -505,17 +506,18 @@ static int cleanup_tmpdir(const char *tmpdir, const char *src, + strdup_or_err(args, 1, "--exclude=.X11-unix"); + strdup_or_err(args, 2, "-utrlHDq"); + strdup_or_err(args, 3, "--delete"); +- if (asprintf(&args[4], "%s/", tmpdir) == -1) { ++ strdup_or_err(args, 4, "--"); ++ if (asprintf(&args[5], "%s/", tmpdir) == -1) { + fprintf(stderr, _("Out of memory\n")); + free_args(args); + return 1; + } +- if (asprintf(&args[5], "%s/", src) == -1) { ++ if (asprintf(&args[6], "%s/", src) == -1) { + fprintf(stderr, _("Out of memory\n")); + free_args(args); + return 1; + } +- args[6] = NULL; ++ args[7] = NULL; + + if (spawn_command(args, pwd->pw_uid) != 0) { + fprintf(stderr, _("Failed to copy files from the runtime temporary directory\n")); +-- +2.54.0 + diff --git a/0015-sandbox-seunshare-fix-getopt-flags.patch b/0015-sandbox-seunshare-fix-getopt-flags.patch new file mode 100644 index 0000000..93e5a23 --- /dev/null +++ b/0015-sandbox-seunshare-fix-getopt-flags.patch @@ -0,0 +1,44 @@ +From 9c29653dc6b0f4e9a74cd119d60a84ed8c4a6ec6 Mon Sep 17 00:00:00 2001 +From: Stephen Smalley +Date: Wed, 13 May 2026 09:59:08 -0400 +Subject: [PATCH] sandbox/seunshare: fix getopt flags +Content-type: text/plain + +-k, -v, and -C do NOT accept an argument, and the optstring +was not correct. + +Signed-off-by: Stephen Smalley +--- + sandbox/seunshare.c | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/sandbox/seunshare.c b/sandbox/seunshare.c +index a48c88036088..0dfc5cb2b820 100644 +--- a/sandbox/seunshare.c ++++ b/sandbox/seunshare.c +@@ -776,10 +776,10 @@ int main(int argc, char **argv) { + {"homedir", 1, 0, 'h'}, + {"tmpdir", 1, 0, 't'}, + {"runuserdir", 1, 0, 'r'}, +- {"kill", 1, 0, 'k'}, +- {"verbose", 1, 0, 'v'}, ++ {"kill", 0, 0, 'k'}, ++ {"verbose", 0, 0, 'v'}, + {"context", 1, 0, 'Z'}, +- {"capabilities", 1, 0, 'C'}, ++ {"capabilities", 0, 0, 'C'}, + {"wayland", 1, 0, 'W'}, + {"pipewire", 1, 0, 'P'}, + {NULL, 0, 0, 0} +@@ -811,7 +811,7 @@ int main(int argc, char **argv) { + } + + while (1) { +- clflag = getopt_long(argc, argv, "Ccvh:r:t:W:Z:", long_options, NULL); ++ clflag = getopt_long(argc, argv, "Ckvh:r:t:W:P:Z:", long_options, NULL); + if (clflag == -1) + break; + +-- +2.54.0 + diff --git a/0016-sandbox-seunshare-prevent-path-traversal-via-W-P.patch b/0016-sandbox-seunshare-prevent-path-traversal-via-W-P.patch new file mode 100644 index 0000000..94c6a72 --- /dev/null +++ b/0016-sandbox-seunshare-prevent-path-traversal-via-W-P.patch @@ -0,0 +1,47 @@ +From 5da4d90480164c13a5bc8f8c820e701790499ecc Mon Sep 17 00:00:00 2001 +From: Stephen Smalley +Date: Wed, 13 May 2026 10:13:17 -0400 +Subject: [PATCH] sandbox/seunshare: prevent path traversal via -W/-P +Content-type: text/plain + +The -W/-P options specify the wayland or pipewire socket name respectively. +The option argument is then concatenated with the runuserdir to create +a pathname. We don't want to allow it to be used to construct arbitrary +paths outside of the runuserdir. Also, check and handle +errors when bind mounting the pipewire socket file. + +Signed-off-by: Stephen Smalley +--- + sandbox/seunshare.c | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/sandbox/seunshare.c b/sandbox/seunshare.c +index 0dfc5cb2b820..945a3b21c1cc 100644 +--- a/sandbox/seunshare.c ++++ b/sandbox/seunshare.c +@@ -849,6 +849,12 @@ int main(int argc, char **argv) { + } + } + ++ if ((wayland_display && (strchr(wayland_display, '/') || strstr(wayland_display, ".."))) || ++ (pipewire_socket && (strchr(pipewire_socket, '/') || strstr(pipewire_socket, "..")))) { ++ fprintf(stderr, _("Error: -W/-P must be a socket name, not a path\n")); ++ return -1; ++ } ++ + if (! homedir_s && ! tmpdir_s) { + fprintf(stderr, _("Error: tmpdir and/or homedir required\n %s\n"), USAGE_STRING); + return -1; +@@ -990,7 +996,8 @@ int main(int argc, char **argv) { + perror(_("Out of memory")); + goto childerr; + } +- seunshare_mount_file(pipewire_path, pipewire_path_s); ++ if (seunshare_mount_file(pipewire_path, pipewire_path_s) == -1) ++ goto childerr; + } + } + +-- +2.54.0 + diff --git a/0017-sandbox-seunshare-verify-RUNTIME_DIR-before-use.patch b/0017-sandbox-seunshare-verify-RUNTIME_DIR-before-use.patch new file mode 100644 index 0000000..ed46220 --- /dev/null +++ b/0017-sandbox-seunshare-verify-RUNTIME_DIR-before-use.patch @@ -0,0 +1,38 @@ +From cceeb1def62ef78fed802684e9500cfa1aefa6b3 Mon Sep 17 00:00:00 2001 +From: Stephen Smalley +Date: Wed, 13 May 2026 10:30:20 -0400 +Subject: [PATCH] sandbox/seunshare: verify RUNTIME_DIR before use +Content-type: text/plain + +RUNTIME_DIR can be inherited from XDG_RUNTIME_DIR or set to a default +path. Regardless, we should verify it the same way as the other +user-supplied directories before first use. + +Signed-off-by: Stephen Smalley +--- + sandbox/seunshare.c | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/sandbox/seunshare.c b/sandbox/seunshare.c +index 945a3b21c1cc..89180d0aa1ed 100644 +--- a/sandbox/seunshare.c ++++ b/sandbox/seunshare.c +@@ -964,6 +964,15 @@ int main(int argc, char **argv) { + } + } + ++ if (runuserdir_s) { ++ struct stat sb; ++ ++ if (verify_directory(RUNTIME_DIR, NULL, &sb) < 0 || ++ check_owner_uid(uid, RUNTIME_DIR, &sb) < 0) ++ goto childerr; ++ ++ } ++ + if ((XDG_SESSION_TYPE = getenv("XDG_SESSION_TYPE")) != NULL) { + if ((XDG_SESSION_TYPE = strdup(XDG_SESSION_TYPE)) == NULL) { + perror(_("Out of memory")); +-- +2.54.0 + diff --git a/0018-sandbox-seunshare-drop-unused-runuserdir_r.patch b/0018-sandbox-seunshare-drop-unused-runuserdir_r.patch new file mode 100644 index 0000000..507db08 --- /dev/null +++ b/0018-sandbox-seunshare-drop-unused-runuserdir_r.patch @@ -0,0 +1,48 @@ +From c2f54aff134bcba8ae9701d26d7711530d72be76 Mon Sep 17 00:00:00 2001 +From: Stephen Smalley +Date: Wed, 13 May 2026 10:37:56 -0400 +Subject: [PATCH] sandbox/seunshare: drop unused runuserdir_r +Content-type: text/plain + +runuserdir_r is created but never used nor deleted. Get rid of it. + +Signed-off-by: Stephen Smalley +--- + sandbox/seunshare.c | 8 -------- + 1 file changed, 8 deletions(-) + +diff --git a/sandbox/seunshare.c b/sandbox/seunshare.c +index 89180d0aa1ed..2eef0e2f800e 100644 +--- a/sandbox/seunshare.c ++++ b/sandbox/seunshare.c +@@ -763,14 +763,12 @@ int main(int argc, char **argv) { + char *tmpdir_s = NULL; /* tmpdir spec'd by user in argv[] */ + char *tmpdir_r = NULL; /* tmpdir created by seunshare */ + char *runuserdir_s = NULL; /* /var/run/user/UID spec'd by user in argv[] */ +- char *runuserdir_r = NULL; /* /var/run/user/UID created by seunshare */ + + struct stat st_curhomedir; + struct stat st_homedir; + struct stat st_tmpdir_s; + struct stat st_tmpdir_r; + struct stat st_runuserdir_s; +- struct stat st_runuserdir_r; + + const struct option long_options[] = { + {"homedir", 1, 0, 'h'}, +@@ -902,12 +900,6 @@ int main(int argc, char **argv) { + fprintf(stderr, _("Failed to create runtime temporary directory\n")); + return -1; + } +- /* create runtime runuserdir */ +- if (runuserdir_s && (runuserdir_r = create_tmpdir(runuserdir_s, &st_runuserdir_s, +- &st_runuserdir_r, pwd, execcon)) == NULL) { +- fprintf(stderr, _("Failed to create runtime $XDG_RUNTIME_DIR directory\n")); +- return -1; +- } + + /* spawn child process */ + child = fork(); +-- +2.54.0 + diff --git a/0019-sandbox-seunshare-fix-killall-realloc-and-missing-ty.patch b/0019-sandbox-seunshare-fix-killall-realloc-and-missing-ty.patch new file mode 100644 index 0000000..aab56f7 --- /dev/null +++ b/0019-sandbox-seunshare-fix-killall-realloc-and-missing-ty.patch @@ -0,0 +1,95 @@ +From 47a8bd5d29455f1b8496a2bc7ba95f3f535c2ba7 Mon Sep 17 00:00:00 2001 +From: Stephen Smalley +Date: Wed, 13 May 2026 12:21:47 -0400 +Subject: [PATCH] sandbox/seunshare: fix killall() realloc and missing type + comparison +Content-type: text/plain + +The killall() realloc() can produce an integer overflow. +Check and handle this correctly. + +The killall() logic also only compares the MCS category set when +deciding whether to kill the process. We should at least also compare +the type to avoid incorrectly killing an unrelated process with +the same category set (e.g. if multiple applications are independently +assigning category sets on the same system). Check the type as well. + +killall() still seems error prone and I couldn't find any actual users +of the -k/--kill option for seunshare. Can we just drop this? + +Signed-off-by: Stephen Smalley +--- + sandbox/seunshare.c | 26 +++++++++++++++++++------- + 1 file changed, 19 insertions(+), 7 deletions(-) + +diff --git a/sandbox/seunshare.c b/sandbox/seunshare.c +index 2eef0e2f800e..e20c0f8723aa 100644 +--- a/sandbox/seunshare.c ++++ b/sandbox/seunshare.c +@@ -680,8 +680,8 @@ killall (const char *execcon) + char *scon; + struct dirent *de; + pid_t *pid_table, pid, self; +- int i; +- int pids, max_pids; ++ unsigned int i; ++ unsigned int pids, max_pids; + int running = 0; + self = getpid(); + if (!(dir = opendir(PROC_BASE))) { +@@ -701,26 +701,34 @@ killall (const char *execcon) + return -1; + } + const char *const mcs = context_range_get(con); +- if (!mcs) { ++ const char *const type = context_type_get(con); ++ if (!mcs || !type) { + context_free(con); + free(pid_table); + (void)closedir(dir); + return -1; + } +- printf("mcs=%s\n", mcs); ++ if (verbose) ++ printf("mcs=%s type=%s\n", mcs, type); + while ((de = readdir (dir)) != NULL) { + if (!(pid = (pid_t)atoi(de->d_name)) || pid == self) + continue; + + if (pids == max_pids) { +- pid_t *new_pid_table = realloc(pid_table, 2*pids*sizeof(pid_t)); ++ max_pids *= 2; ++ if (max_pids <= pids) ++ { ++ free(pid_table); ++ (void)closedir(dir); ++ return -1; ++ } ++ pid_t *new_pid_table = reallocarray(pid_table, max_pids, sizeof(pid_t)); + if (!new_pid_table) { + free(pid_table); + (void)closedir(dir); + return -1; + } + pid_table = new_pid_table; +- max_pids *= 2; + } + pid_table[pids++] = pid; + } +@@ -734,8 +742,12 @@ killall (const char *execcon) + + context_t pidcon = context_new(scon); + if (pidcon) { ++ const char *const pmcs = context_range_get(pidcon); ++ const char *const ptype = context_type_get(pidcon); ++ + /* Attempt to kill remaining processes */ +- if (strcmp(context_range_get(pidcon), mcs) == 0) ++ if (pmcs && ptype && !strcmp(pmcs, mcs) && ++ !strcmp(ptype, type)) + kill(id, SIGKILL); + + context_free(pidcon); +-- +2.54.0 + diff --git a/0020-sandbox-seunshare-rewrite-to-pin-directories-before-.patch b/0020-sandbox-seunshare-rewrite-to-pin-directories-before-.patch new file mode 100644 index 0000000..56b8e6d --- /dev/null +++ b/0020-sandbox-seunshare-rewrite-to-pin-directories-before-.patch @@ -0,0 +1,606 @@ +From 9c44bb929a7937897a787790ece679464345c5d9 Mon Sep 17 00:00:00 2001 +From: Stephen Smalley +Date: Thu, 14 May 2026 10:32:13 -0400 +Subject: [PATCH] sandbox/seunshare: rewrite to pin directories before use +Content-type: text/plain + +To fully eliminate TOCTOU issues in seunshare, we need to pin the +directories and pass them via /proc/self/fd/N to mount(2). +This is complicated further by the unsharing of the mount namespace +in the child process and the need to remount in order to apply +nosuid/nodev/noexec flags. Do the best we can with the legacy +mount(2) API for compatibility on old kernels and revisit if/when +we make kernels with the new mount API the minimum required for +SELinux userspace. + +Signed-off-by: Stephen Smalley +--- + sandbox/seunshare.c | 406 ++++++++++++++++++++++++++------------------ + 1 file changed, 241 insertions(+), 165 deletions(-) + +diff --git a/sandbox/seunshare.c b/sandbox/seunshare.c +index e20c0f8723aa..611bfe80d030 100644 +--- a/sandbox/seunshare.c ++++ b/sandbox/seunshare.c +@@ -197,35 +197,33 @@ static int check_owner_gid(gid_t gid, const char *file, struct stat *st) { + return 0; + } + +-#define equal_stats(one,two) \ +- ((one)->st_dev == (two)->st_dev && (one)->st_ino == (two)->st_ino && \ +- (one)->st_uid == (two)->st_uid && (one)->st_gid == (two)->st_gid && \ +- (one)->st_mode == (two)->st_mode) +- + /** +- * Sanity check specified directory. Store stat info for future comparison, or +- * compare with previously saved info to detect replaced directories. +- * Note: This function does not perform owner checks. ++ * Open directory with O_DIRECTORY|O_NOFOLLOW and return its fd ++ * and fstat() results. The returned fd and its /proc/self/fd/N ++ * path can be used for all subsequent operations on the directory. ++ * NB Any non-final components in the @dir pathname are resolved ++ * as usual but will be checked against the fsuid of the caller. + */ +-static int verify_directory(const char *dir, struct stat *st_in, struct stat *st_out) { ++static int pin_dir(const char *dir, struct stat *st_out) ++{ ++ int fd; + struct stat sb; + +- if (st_out == NULL) st_out = &sb; +- +- if (lstat(dir, st_out) == -1) { +- fprintf(stderr, _("Failed to stat %s: %s\n"), dir, strerror(errno)); ++ fd = open(dir, O_RDONLY|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); ++ if (fd < 0) { ++ fprintf(stderr, _("Failed to open %s: %m\n"), dir); + return -1; + } +- if (! S_ISDIR(st_out->st_mode)) { +- fprintf(stderr, _("Error: %s is not a directory: %s\n"), dir, strerror(errno)); +- return -1; +- } +- if (st_in && !equal_stats(st_in, st_out)) { +- fprintf(stderr, _("Error: %s was replaced by a different directory\n"), dir); ++ ++ if (fstat(fd, &sb) < 0) { ++ fprintf(stderr, _("Failed to stat %s: %m\n"), dir); ++ close(fd); + return -1; + } + +- return 0; ++ if (st_out) ++ *st_out = sb; ++ return fd; + } + + /** +@@ -256,85 +254,90 @@ static int verify_shell(const char *shell_name) + } + + /** +- * Mount directory and check that we mounted the right directory. ++ * Bind-mount directory @src (using @src_fd) on directory @dst (using @dst_fd), ++ * applying @bind_flags for the initial bind mount and @sec_flags if ++ * non-zero via remount. + */ +-static int seunshare_mount(const char *src, const char *dst, struct stat *src_st) ++static int seunshare_mount(const char *src, int src_fd, ++ const char *dst, int dst_fd, ++ int bind_flags, int sec_flags) + { +- int bind_flags = MS_BIND; +- int sec_flags = 0; +- int is_tmp = 0; ++ char srcprocfd[32], dstprocfd[32]; ++ ++ bind_flags |= MS_BIND; + + if (verbose) + printf(_("Mounting %s on %s\n"), src, dst); + +- if (strcmp("/tmp", dst) == 0) { +- sec_flags = MS_NODEV | MS_NOSUID | MS_NOEXEC; +- is_tmp = 1; +- } ++ snprintf(srcprocfd, sizeof(srcprocfd), "/proc/self/fd/%d", src_fd); ++ snprintf(dstprocfd, sizeof(dstprocfd), "/proc/self/fd/%d", dst_fd); + +- if (strncmp("/run/user", dst, 9) == 0) { +- bind_flags |= MS_REC; +- } +- +- /* mount directory */ +- if (mount(src, dst, NULL, bind_flags, NULL) < 0) { +- fprintf(stderr, _("Failed to mount %s on %s: %s\n"), src, dst, strerror(errno)); ++ /* bind mount directory */ ++ if (mount(srcprocfd, dstprocfd, NULL, bind_flags, NULL) < 0) { ++ fprintf(stderr, _("Failed to mount %s on %s: %m\n"), src, dst); + return -1; + } +- /* remount with security flags, ignored on original bind mount */ +- if (sec_flags && mount(NULL, dst, NULL, MS_BIND | MS_REMOUNT | sec_flags, NULL) < 0) { ++ ++ /* ++ * Remount with security flags set - requires use of dst path again. ++ * Revisit when we migrate to open_tree()/move_mount(). ++ */ ++ if (sec_flags && ++ mount(NULL, dst, NULL, MS_BIND | MS_REMOUNT | sec_flags, NULL) < 0) { + fprintf(stderr, _("Failed to remount %s: %m\n"), dst); + return -1; + } + +- /* verify whether we mounted what we expected to mount */ +- if (verify_directory(dst, src_st, NULL) < 0) return -1; +- +- /* bind mount /tmp on /var/tmp too */ +- if (is_tmp) { +- if (verbose) +- printf(_("Mounting /tmp on /var/tmp\n")); +- +- if (mount("/tmp", "/var/tmp", NULL, MS_BIND, NULL) < 0) { +- fprintf(stderr, _("Failed to mount /tmp on /var/tmp: %s\n"), strerror(errno)); +- return -1; +- } +- /* remount with security flags, ignored on original bind mount */ +- if (mount(NULL, "/var/tmp", NULL, MS_BIND | MS_REMOUNT | sec_flags, NULL) < 0) { +- fprintf(stderr, _("Failed to remount /var/tmp: %m\n")); +- return -1; +- } +- } +- + return 0; +- + } + + /** +- * Mount directory and check that we mounted the right directory. ++ * Bind-mount a file named @src_name in directory @src_dirfd on ++ * a file named @dst_name in directory @dst_dirfd, creating @dst_name ++ * if it doesn't already exist. + */ +-static int seunshare_mount_file(const char *src, const char *dst) ++static int seunshare_mount_file(int src_dirfd, const char *src_name, ++ int dst_dirfd, const char *dst_name) + { ++ char srcprocfd[32], dstprocfd[32]; ++ int src_fd = -1, dst_fd = -1, rc = -1; ++ + if (verbose) +- printf(_("Mounting %s on %s\n"), src, dst); ++ printf(_("Mounting %s on %s\n"), src_name, dst_name); + +- if (access(dst, F_OK) == -1) { +- int fd = open(dst, O_WRONLY | O_CREAT | O_NOFOLLOW | O_CLOEXEC, 0600); +- if (fd < 0) { +- fprintf(stderr, _("Failed to create mount point %s: %m\n"), dst); +- return -1; +- } +- close(fd); ++ src_fd = openat(src_dirfd, src_name, O_PATH | O_NOFOLLOW | O_CLOEXEC); ++ if (src_fd < 0) { ++ fprintf(stderr, _("Failed to open %s: %m\n"), src_name); ++ goto out; + } + +- /* mount file */ +- if (mount(src, dst, NULL, MS_BIND, NULL) < 0) { +- fprintf(stderr, _("Failed to mount %s on %s: %s\n"), src, dst, strerror(errno)); +- return -1; ++ dst_fd = openat(dst_dirfd, dst_name, O_PATH | O_NOFOLLOW | O_CLOEXEC); ++ if (dst_fd < 0 && errno == ENOENT) ++ dst_fd = openat(dst_dirfd, dst_name, ++ O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW | ++ O_CLOEXEC, 0600); ++ if (dst_fd < 0) { ++ fprintf(stderr, _("Failed to open/create %s: %m\n"), dst_name); ++ goto out; + } + +- return 0; ++ snprintf(srcprocfd, sizeof(srcprocfd), "/proc/self/fd/%d", src_fd); ++ snprintf(dstprocfd, sizeof(dstprocfd), "/proc/self/fd/%d", dst_fd); + ++ /* mount file */ ++ if (mount(srcprocfd, dstprocfd, NULL, MS_BIND, NULL) < 0) { ++ fprintf(stderr, _("Failed to mount %s on %s: %m\n"), src_name, ++ dst_name); ++ goto out; ++ } ++ ++ rc = 0; ++out: ++ if (src_fd >= 0) ++ close(src_fd); ++ if (dst_fd >= 0) ++ close(dst_fd); ++ return rc; + } + + /* +@@ -556,7 +559,8 @@ err: + * to clean it up. + */ + static char *create_tmpdir(const char *src, struct stat *src_st, +- struct stat *out_st, struct passwd *pwd, const char *execcon) ++ struct stat *out_st, struct passwd *pwd, ++ const char *execcon) + { + char *tmpdir = NULL; + char **cmd = NULL; +@@ -564,29 +568,23 @@ static char *create_tmpdir(const char *src, struct stat *src_st, + struct stat tmp_st; + char *con = NULL; + +- /* get selinux context */ ++ /* get selinux context of source directory */ + if (execcon) { + if ((uid_t)setfsuid(pwd->pw_uid) != 0) + goto err; +- +- if ((fd_s = open(src, O_RDONLY)) < 0) { +- fprintf(stderr, _("Failed to open directory %s: %s\n"), src, strerror(errno)); +- goto err; +- } +- if (fstat(fd_s, &tmp_st) == -1) { +- fprintf(stderr, _("Failed to stat directory %s: %s\n"), src, strerror(errno)); ++ if ((fd_s = pin_dir(src, &tmp_st)) < 0) + goto err; +- } +- if (!equal_stats(src_st, &tmp_st)) { +- fprintf(stderr, _("Error: %s was replaced by a different directory\n"), src); ++ if (tmp_st.st_dev != src_st->st_dev || ++ tmp_st.st_ino != src_st->st_ino) { ++ fprintf(stderr, ++ _("%s was replaced by a different directory\n"), ++ src); + goto err; + } + if (fgetfilecon(fd_s, &con) == -1) { +- fprintf(stderr, _("Failed to get context of the directory %s: %s\n"), src, strerror(errno)); ++ fprintf(stderr, _("Failed to get context of the directory %s: %m\n"), src); + goto err; + } +- +- /* ok to not reach this if there is an error */ + if ((uid_t)setfsuid(0) != pwd->pw_uid) + goto err; + } +@@ -602,9 +600,9 @@ static char *create_tmpdir(const char *src, struct stat *src_st, + } + + /* temporary directory must be owned by root:user */ +- if (verify_directory(tmpdir, NULL, out_st) < 0) { ++ fd_t = pin_dir(tmpdir, out_st); ++ if (fd_t < 0) + goto err; +- } + + if (check_owner_uid(0, tmpdir, out_st) < 0) + goto err; +@@ -613,18 +611,6 @@ static char *create_tmpdir(const char *src, struct stat *src_st, + goto err; + + /* change permissions of the temporary directory */ +- if ((fd_t = open(tmpdir, O_RDONLY)) < 0) { +- fprintf(stderr, _("Failed to open directory %s: %s\n"), tmpdir, strerror(errno)); +- goto err; +- } +- if (fstat(fd_t, &tmp_st) == -1) { +- fprintf(stderr, _("Failed to stat directory %s: %s\n"), tmpdir, strerror(errno)); +- goto err; +- } +- if (!equal_stats(out_st, &tmp_st)) { +- fprintf(stderr, _("Error: %s was replaced by a different directory\n"), tmpdir); +- goto err; +- } + if (fchmod(fd_t, 01770) == -1) { + fprintf(stderr, _("Unable to change mode on %s: %s\n"), tmpdir, strerror(errno)); + goto err; +@@ -664,10 +650,10 @@ static char *create_tmpdir(const char *src, struct stat *src_st, + err: + free(tmpdir); tmpdir = NULL; + good: +- free_args(cmd); +- freecon(con); con = NULL; + if (fd_t >= 0) close(fd_t); + if (fd_s >= 0) close(fd_s); ++ free_args(cmd); ++ freecon(con); con = NULL; + return tmpdir; + } + +@@ -776,12 +762,13 @@ int main(int argc, char **argv) { + char *tmpdir_r = NULL; /* tmpdir created by seunshare */ + char *runuserdir_s = NULL; /* /var/run/user/UID spec'd by user in argv[] */ + +- struct stat st_curhomedir; + struct stat st_homedir; + struct stat st_tmpdir_s; + struct stat st_tmpdir_r; + struct stat st_runuserdir_s; + ++ int fd; ++ + const struct option long_options[] = { + {"homedir", 1, 0, 'h'}, + {"tmpdir", 1, 0, 't'}, +@@ -893,24 +880,49 @@ int main(int argc, char **argv) { + return -1; + } + +- /* verify homedir and tmpdir */ +- if (homedir_s && ( +- verify_directory(homedir_s, NULL, &st_homedir) < 0 || +- check_owner_uid(uid, homedir_s, &st_homedir))) return -1; +- if (tmpdir_s && ( +- verify_directory(tmpdir_s, NULL, &st_tmpdir_s) < 0 || +- check_owner_uid(uid, tmpdir_s, &st_tmpdir_s))) return -1; +- if (runuserdir_s && ( +- verify_directory(runuserdir_s, NULL, &st_runuserdir_s) < 0 || +- check_owner_uid(uid, runuserdir_s, &st_runuserdir_s))) return -1; ++ /* ++ * Perform early validation of the caller-provided directories so we ++ * can fail fast, but we unfortunately have to redo this after ++ * unsharing the mount namespace in the child so that it can use ++ * the descriptors for subsequent mount(2) calls. Otherwise, ++ * they end up with a different mount namespace and mount(2) fails ++ * with errno EINVAL. ++ */ ++ if (homedir_s) { ++ fd = pin_dir(homedir_s, &st_homedir); ++ if (fd < 0) ++ return -1; ++ if (check_owner_uid(uid, homedir_s, &st_homedir)) ++ return -1; ++ close(fd); ++ } ++ if (tmpdir_s) { ++ fd = pin_dir(tmpdir_s, &st_tmpdir_s); ++ if (fd < 0) ++ return -1; ++ if (check_owner_uid(uid, tmpdir_s, &st_tmpdir_s)) ++ return -1; ++ close(fd); ++ } ++ if (runuserdir_s) { ++ fd = pin_dir(runuserdir_s, &st_runuserdir_s); ++ if (fd < 0) ++ return -1; ++ if (check_owner_uid(uid, runuserdir_s, &st_runuserdir_s)) ++ return -1; ++ close(fd); ++ } + + if ((uid_t)setfsuid(0) != uid) return -1; + + /* create runtime tmpdir */ +- if (tmpdir_s && (tmpdir_r = create_tmpdir(tmpdir_s, &st_tmpdir_s, +- &st_tmpdir_r, pwd, execcon)) == NULL) { +- fprintf(stderr, _("Failed to create runtime temporary directory\n")); +- return -1; ++ if (tmpdir_s) { ++ tmpdir_r = create_tmpdir(tmpdir_s, &st_tmpdir_s, &st_tmpdir_r, ++ pwd, execcon); ++ if (!tmpdir_r) { ++ fprintf(stderr, _("Failed to create runtime temporary directory\n")); ++ return -1; ++ } + } + + /* spawn child process */ +@@ -927,11 +939,10 @@ int main(int argc, char **argv) { + char *XDG_SESSION_TYPE = NULL; + int rc = -1; + char *resolved_path = NULL; +- char *wayland_path_s = NULL; /* /tmp/.../wayland-0 */ +- char *wayland_path = NULL; /* /run/user/UID/wayland-0 */ +- char *pipewire_path_s = NULL; /* /tmp/.../pipewire-0 */ +- char *pipewire_path = NULL; /* /run/user/UID/pipewire-0 */ +- ++ int fd_homedir_s = -1, fd_curhomedir = -1; ++ int fd_runuserdir_s = -1, fd_runtime_dir = -1; ++ int fd_tmpdir_r = -1, fd_tmp = -1, fd_var_tmp = -1; ++ struct stat sb; + + if (unshare(CLONE_NEWNS) < 0) { + perror(_("Failed to unshare")); +@@ -941,19 +952,67 @@ int main(int argc, char **argv) { + /* Remount / as SLAVE so that nothing mounted in the namespace + shows up in the parent */ + if (mount("none", "/", NULL, MS_SLAVE | MS_REC , NULL) < 0) { +- perror(_("Failed to make / a SLAVE mountpoint\n")); ++ + goto childerr; + } + + /* assume fsuid==ruid after this point */ + if ((uid_t)setfsuid(uid) != 0) goto childerr; + ++ /* ++ * Now we can pin the source directories in this namespace ++ * for later use by mount(2). We recheck that each ++ * directory is the same inode and still has the ++ * expected ownership as the early validation. ++ */ ++ if (homedir_s) { ++ fd_homedir_s = pin_dir(homedir_s, &sb); ++ if (fd_homedir_s < 0) ++ goto childerr; ++ if (sb.st_dev != st_homedir.st_dev || ++ sb.st_ino != st_homedir.st_ino) ++ goto childerr; ++ if (check_owner_uid(uid, homedir_s, &sb)) ++ goto childerr; ++ } ++ /* ++ * NB We don't need to re-pin tmpdir_s, just tmpdir_r, ++ * since the child never uses tmpdir_s. ++ */ ++ if (tmpdir_r) { ++ fd_tmpdir_r = pin_dir(tmpdir_r, &sb); ++ if (fd < 0) ++ goto childerr; ++ /* ++ * tmpdir_r checks differ in that it is ++ * root-owned and we also want to validate ++ * that the mode is still correct. ++ */ ++ if (sb.st_dev != st_tmpdir_r.st_dev || ++ sb.st_ino != st_tmpdir_r.st_ino || ++ sb.st_mode != st_tmpdir_r.st_mode) ++ goto childerr; ++ if (check_owner_uid(0, tmpdir_r, &sb)) ++ goto childerr; ++ } ++ if (runuserdir_s) { ++ fd_runuserdir_s = pin_dir(runuserdir_s, &sb); ++ if (fd_runuserdir_s < 0) ++ goto childerr; ++ if (sb.st_dev != st_runuserdir_s.st_dev || ++ sb.st_ino != st_runuserdir_s.st_ino) ++ goto childerr; ++ if (check_owner_uid(uid, runuserdir_s, &sb)) ++ goto childerr; ++ } ++ + resolved_path = realpath(pwd->pw_dir,NULL); + if (! resolved_path) goto childerr; + +- if (verify_directory(resolved_path, NULL, &st_curhomedir) < 0) ++ fd_curhomedir = pin_dir(resolved_path, &sb); ++ if (fd_curhomedir < 0) + goto childerr; +- if (check_owner_uid(uid, resolved_path, &st_curhomedir) < 0) ++ if (check_owner_uid(uid, resolved_path, &sb) < 0) + goto childerr; + + if ((RUNTIME_DIR = getenv("XDG_RUNTIME_DIR")) != NULL) { +@@ -969,12 +1028,11 @@ int main(int argc, char **argv) { + } + + if (runuserdir_s) { +- struct stat sb; +- +- if (verify_directory(RUNTIME_DIR, NULL, &sb) < 0 || +- check_owner_uid(uid, RUNTIME_DIR, &sb) < 0) ++ fd_runtime_dir = pin_dir(RUNTIME_DIR, &sb); ++ if (fd_runtime_dir < 0) ++ goto childerr; ++ if (check_owner_uid(uid, RUNTIME_DIR, &sb) < 0) + goto childerr; +- + } + + if ((XDG_SESSION_TYPE = getenv("XDG_SESSION_TYPE")) != NULL) { +@@ -985,42 +1043,57 @@ int main(int argc, char **argv) { + } + + if (runuserdir_s && (wayland_display || pipewire_socket)) { +- if (wayland_display) { +- if (asprintf(&wayland_path_s, "%s/%s", runuserdir_s, wayland_display) == -1) { +- perror(_("Out of memory")); ++ if (wayland_display && ++ seunshare_mount_file(fd_runtime_dir, ++ wayland_display, ++ fd_runuserdir_s, ++ wayland_display) == -1) + goto childerr; +- } + +- if (asprintf(&wayland_path, "%s/%s", RUNTIME_DIR, wayland_display) == -1) { +- perror(_("Out of memory")); +- goto childerr; +- } ++ if (pipewire_socket && ++ seunshare_mount_file(fd_runtime_dir, ++ "pipewire-0", ++ fd_runuserdir_s, ++ pipewire_socket) == -1) ++ goto childerr; ++ } + +- if (seunshare_mount_file(wayland_path, wayland_path_s) == -1) +- goto childerr; ++ /* mount homedir, runuserdir and tmpdir, in this order */ ++ if (runuserdir_s && ++ seunshare_mount(runuserdir_s, fd_runuserdir_s, ++ RUNTIME_DIR, fd_runtime_dir, ++ MS_REC, 0) != 0) ++ goto childerr; ++ if (homedir_s && ++ seunshare_mount(homedir_s, fd_homedir_s, ++ resolved_path, fd_curhomedir, ++ 0, 0) != 0) ++ goto childerr; ++ if (tmpdir_s) { ++ fd_tmp = open("/tmp", O_RDONLY | O_DIRECTORY | ++ O_NOFOLLOW | O_CLOEXEC); ++ if (fd_tmp < 0) { ++ perror(_("Failed to open /tmp")); ++ goto childerr; + } + +- if (pipewire_socket) { +- if (asprintf(&pipewire_path_s, "%s/%s", runuserdir_s, pipewire_socket) == -1) { +- perror(_("Out of memory")); +- goto childerr; +- } +- if (asprintf(&pipewire_path, "%s/pipewire-0", RUNTIME_DIR) == -1) { +- perror(_("Out of memory")); +- goto childerr; +- } +- if (seunshare_mount_file(pipewire_path, pipewire_path_s) == -1) +- goto childerr; ++ if (seunshare_mount(tmpdir_r, fd_tmpdir_r, ++ "/tmp", fd_tmp, 0, ++ MS_NODEV|MS_NOSUID|MS_NOEXEC) < 0) ++ goto childerr; ++ ++ fd_var_tmp = open("/var/tmp", O_RDONLY | O_DIRECTORY | ++ O_NOFOLLOW | O_CLOEXEC); ++ if (fd_var_tmp < 0) { ++ perror(_("Failed to open /var/tmp")); ++ goto childerr; + } +- } + +- /* mount homedir, runuserdir and tmpdir, in this order */ +- if (runuserdir_s && seunshare_mount(runuserdir_s, RUNTIME_DIR, +- &st_runuserdir_s) != 0) goto childerr; +- if (homedir_s && seunshare_mount(homedir_s, resolved_path, +- &st_homedir) != 0) goto childerr; +- if (tmpdir_s && seunshare_mount(tmpdir_r, "/tmp", +- &st_tmpdir_r) != 0) goto childerr; ++ if (seunshare_mount("/tmp", fd_tmpdir_r, ++ "/var/tmp", fd_var_tmp, 0, ++ MS_NODEV|MS_NOSUID|MS_NOEXEC) < 0) ++ goto childerr; ++ } + + if (drop_privs(uid) != 0) goto childerr; + +@@ -1101,11 +1174,14 @@ int main(int argc, char **argv) { + execv(argv[optind], argv + optind); + fprintf(stderr, _("Failed to execute command %s: %s\n"), argv[optind], strerror(errno)); + childerr: ++ if (fd_homedir_s >= 0) close(fd_homedir_s); ++ if (fd_curhomedir >= 0) close(fd_curhomedir); ++ if (fd_runuserdir_s >= 0) close(fd_runuserdir_s); ++ if (fd_runtime_dir >= 0) close(fd_runtime_dir); ++ if (fd_tmpdir_r >= 0) close(fd_tmpdir_r); ++ if (fd_tmp >= 0) close(fd_tmp); ++ if (fd_var_tmp >= 0) close(fd_var_tmp); + free(resolved_path); +- free(wayland_path); +- free(wayland_path_s); +- free(pipewire_path); +- free(pipewire_path_s); + free(display); + free(LANG); + free(RUNTIME_DIR); +-- +2.54.0 + diff --git a/0021-sandbox-seunshare-fully-check-setfsuid-calls.patch b/0021-sandbox-seunshare-fully-check-setfsuid-calls.patch new file mode 100644 index 0000000..2c7d50b --- /dev/null +++ b/0021-sandbox-seunshare-fully-check-setfsuid-calls.patch @@ -0,0 +1,158 @@ +From 59cfed3bf23511f792bde3085759ab44ed643a4a Mon Sep 17 00:00:00 2001 +From: Stephen Smalley +Date: Thu, 14 May 2026 11:03:40 -0400 +Subject: [PATCH] sandbox/seunshare: fully check setfsuid() calls +Content-type: text/plain + +setfsuid() returns the old fsuid value and doesn't always +set errno. The existing code was checking that it did +successfully return the old fsuid value but not explicitly +checking that the new one was set, which can be done by +a second call to setfsuid() with -1 and checking its +return value. Add a wrapper for setfsuid() and use it +throughout to ensure complete checking. + +Signed-off-by: Stephen Smalley +--- + sandbox/seunshare.c | 60 ++++++++++++++++++++++++++++++++++++--------- + 1 file changed, 48 insertions(+), 12 deletions(-) + +diff --git a/sandbox/seunshare.c b/sandbox/seunshare.c +index 611bfe80d030..98fcef326853 100644 +--- a/sandbox/seunshare.c ++++ b/sandbox/seunshare.c +@@ -486,6 +486,44 @@ static bool rm_rf(int targetfd, const char *path) { + return true; + } + ++/* ++ * setfsuid() returns the previous fsuid value, ++ * and does not reliably set errno on errors. ++ * Let's do better. ++ */ ++static int setfsuid_checked(uid_t old, uid_t new) ++{ ++ int rc; ++ ++ rc = setfsuid(new); ++ if ((uid_t)rc != old) { ++ int save_errno = errno; ++ fprintf(stderr, ++ "setfsuid(%u): Returned unexpected old uid %u\n", ++ new, (uid_t)rc); ++ if (save_errno) ++ errno = save_errno; ++ else ++ errno = EPERM; ++ return -1; ++ } ++ ++ rc = setfsuid(-1); ++ if ((uid_t)rc != new) { ++ int save_errno = errno; ++ fprintf(stderr, ++ "setfsuid(%u): Produced unexpected new uid %u\n", ++ new,(uid_t)rc); ++ if (save_errno) ++ errno = save_errno; ++ else ++ errno = EPERM; ++ return -1; ++ } ++ ++ return 0; ++} ++ + /** + * Clean up runtime temporary directory. Returns 0 if no problem was detected, + * >0 if some error was detected, but errors here are treated as non-fatal and +@@ -529,10 +567,8 @@ static int cleanup_tmpdir(const char *tmpdir, const char *src, + free_args(args); + } + +- if ((uid_t)setfsuid(0) != 0) { +- /* setfsuid does not return error, but this check makes code checkers happy */ ++ if (setfsuid_checked(0, 0) < 0) + rc++; +- } + + /* Recursively remove the runtime temp directory. */ + if (!rm_rf(AT_FDCWD, tmpdir)) { +@@ -540,7 +576,7 @@ static int cleanup_tmpdir(const char *tmpdir, const char *src, + rc++; + } + +- if ((uid_t)setfsuid(pwd->pw_uid) != 0) { ++ if (setfsuid_checked(0, pwd->pw_uid) < 0) { + fprintf(stderr, _("unable to switch back to user after clearing tmp dir\n")); + rc++; + } +@@ -570,7 +606,7 @@ static char *create_tmpdir(const char *src, struct stat *src_st, + + /* get selinux context of source directory */ + if (execcon) { +- if ((uid_t)setfsuid(pwd->pw_uid) != 0) ++ if (setfsuid_checked(0, pwd->pw_uid)) + goto err; + if ((fd_s = pin_dir(src, &tmp_st)) < 0) + goto err; +@@ -585,7 +621,7 @@ static char *create_tmpdir(const char *src, struct stat *src_st, + fprintf(stderr, _("Failed to get context of the directory %s: %m\n"), src); + goto err; + } +- if ((uid_t)setfsuid(0) != pwd->pw_uid) ++ if (setfsuid_checked(pwd->pw_uid, 0) < 0) + goto err; + } + +@@ -629,7 +665,7 @@ static char *create_tmpdir(const char *src, struct stat *src_st, + } + } + +- if ((uid_t)setfsuid(pwd->pw_uid) != 0) ++ if (setfsuid_checked(0, pwd->pw_uid) < 0) + goto err; + + if (rsynccmd(src, tmpdir, &cmd) < 0) { +@@ -637,7 +673,7 @@ static char *create_tmpdir(const char *src, struct stat *src_st, + } + + /* ok to not reach this if there is an error */ +- if ((uid_t)setfsuid(0) != pwd->pw_uid) ++ if (setfsuid_checked(pwd->pw_uid, 0) < 0) + goto err; + + if (spawn_command(cmd, pwd->pw_uid) != 0) { +@@ -874,9 +910,8 @@ int main(int argc, char **argv) { + /* Changing fsuid is usually required when user-specified directory is + * on an NFS mount. It's also desired to avoid leaking info about + * existence of the files not accessible to the user. */ +- if ((uid_t)setfsuid(uid) != 0) { ++ if (setfsuid_checked(0, uid) < 0) { + fprintf(stderr, _("Error: unable to setfsuid\n")); +- + return -1; + } + +@@ -913,7 +948,8 @@ int main(int argc, char **argv) { + close(fd); + } + +- if ((uid_t)setfsuid(0) != uid) return -1; ++ if (setfsuid_checked(uid, 0) < 0) ++ return -1; + + /* create runtime tmpdir */ + if (tmpdir_s) { +@@ -957,7 +993,7 @@ int main(int argc, char **argv) { + } + + /* assume fsuid==ruid after this point */ +- if ((uid_t)setfsuid(uid) != 0) goto childerr; ++ if (setfsuid_checked(0, uid) < 0) goto childerr; + + /* + * Now we can pin the source directories in this namespace +-- +2.54.0 + diff --git a/0022-sandbox-seunshare-check-owner-in-seunshare_mount_fil.patch b/0022-sandbox-seunshare-check-owner-in-seunshare_mount_fil.patch new file mode 100644 index 0000000..95aed7c --- /dev/null +++ b/0022-sandbox-seunshare-check-owner-in-seunshare_mount_fil.patch @@ -0,0 +1,85 @@ +From 8a3de70622d252f1ee070d6d9e9b019b9a6edd08 Mon Sep 17 00:00:00 2001 +From: Stephen Smalley +Date: Fri, 15 May 2026 11:11:43 -0400 +Subject: [PATCH] sandbox/seunshare: check owner in seunshare_mount_file() +Content-type: text/plain + +We currently apply an owner check on directories prior to +calling seunshare_mount() on them. Do the same for +seunshare_mount_file(), but this has to be done inside +of the function since we do not open the source and +destination files until then. + +Signed-off-by: Stephen Smalley +--- + sandbox/seunshare.c | 23 ++++++++++++++++++++--- + 1 file changed, 20 insertions(+), 3 deletions(-) + +diff --git a/sandbox/seunshare.c b/sandbox/seunshare.c +index 98fcef326853..3afe1554ae56 100644 +--- a/sandbox/seunshare.c ++++ b/sandbox/seunshare.c +@@ -296,11 +296,12 @@ static int seunshare_mount(const char *src, int src_fd, + * a file named @dst_name in directory @dst_dirfd, creating @dst_name + * if it doesn't already exist. + */ +-static int seunshare_mount_file(int src_dirfd, const char *src_name, ++static int seunshare_mount_file(uid_t uid, int src_dirfd, const char *src_name, + int dst_dirfd, const char *dst_name) + { + char srcprocfd[32], dstprocfd[32]; + int src_fd = -1, dst_fd = -1, rc = -1; ++ struct stat sb; + + if (verbose) + printf(_("Mounting %s on %s\n"), src_name, dst_name); +@@ -311,6 +312,14 @@ static int seunshare_mount_file(int src_dirfd, const char *src_name, + goto out; + } + ++ if (fstat(src_fd, &sb) < 0) { ++ fprintf(stderr, _("Failed to stat %s: %m\n"), src_name); ++ goto out; ++ ++ } ++ if (check_owner_uid(uid, src_name, &sb)) ++ goto out; ++ + dst_fd = openat(dst_dirfd, dst_name, O_PATH | O_NOFOLLOW | O_CLOEXEC); + if (dst_fd < 0 && errno == ENOENT) + dst_fd = openat(dst_dirfd, dst_name, +@@ -321,6 +330,14 @@ static int seunshare_mount_file(int src_dirfd, const char *src_name, + goto out; + } + ++ if (fstat(dst_fd, &sb) < 0) { ++ fprintf(stderr, _("Failed to stat %s: %m\n"), dst_name); ++ goto out; ++ ++ } ++ if (check_owner_uid(uid, dst_name, &sb)) ++ goto out; ++ + snprintf(srcprocfd, sizeof(srcprocfd), "/proc/self/fd/%d", src_fd); + snprintf(dstprocfd, sizeof(dstprocfd), "/proc/self/fd/%d", dst_fd); + +@@ -1080,14 +1097,14 @@ int main(int argc, char **argv) { + + if (runuserdir_s && (wayland_display || pipewire_socket)) { + if (wayland_display && +- seunshare_mount_file(fd_runtime_dir, ++ seunshare_mount_file(uid, fd_runtime_dir, + wayland_display, + fd_runuserdir_s, + wayland_display) == -1) + goto childerr; + + if (pipewire_socket && +- seunshare_mount_file(fd_runtime_dir, ++ seunshare_mount_file(uid, fd_runtime_dir, + "pipewire-0", + fd_runuserdir_s, + pipewire_socket) == -1) +-- +2.54.0 + diff --git a/0023-sandbox-seunshare-fix-fd_tmpdir_r-check.patch b/0023-sandbox-seunshare-fix-fd_tmpdir_r-check.patch new file mode 100644 index 0000000..f14c4a2 --- /dev/null +++ b/0023-sandbox-seunshare-fix-fd_tmpdir_r-check.patch @@ -0,0 +1,29 @@ +From d99b5a313a2790b1ca703023112f548cbe65617a Mon Sep 17 00:00:00 2001 +From: Stephen Smalley +Date: Tue, 19 May 2026 08:13:40 -0400 +Subject: [PATCH] sandbox/seunshare: fix fd_tmpdir_r check +Content-type: text/plain + +Check fd_tmpdir_r not fd for a failed pin_dir() here. + +Signed-off-by: Stephen Smalley +--- + sandbox/seunshare.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/sandbox/seunshare.c b/sandbox/seunshare.c +index 3afe1554ae56..7a4233fbee00 100644 +--- a/sandbox/seunshare.c ++++ b/sandbox/seunshare.c +@@ -1034,7 +1034,7 @@ int main(int argc, char **argv) { + */ + if (tmpdir_r) { + fd_tmpdir_r = pin_dir(tmpdir_r, &sb); +- if (fd < 0) ++ if (fd_tmpdir_r < 0) + goto childerr; + /* + * tmpdir_r checks differ in that it is +-- +2.54.0 + diff --git a/0024-restorecond-Do-not-unlink-pidfile-if-not-used.patch b/0024-restorecond-Do-not-unlink-pidfile-if-not-used.patch new file mode 100644 index 0000000..cdeef2c --- /dev/null +++ b/0024-restorecond-Do-not-unlink-pidfile-if-not-used.patch @@ -0,0 +1,30 @@ +From a063bc28044a23dd7aa0c496c52a9c9ff4abd349 Mon Sep 17 00:00:00 2001 +From: Petr Lautrbach +Date: Mon, 25 May 2026 18:11:21 +0200 +Subject: [PATCH] restorecond: Do not unlink pidfile if not used +Content-type: text/plain + +With -F or -d, restorecond does not create nor use pidfile and therefore +it should not try to remove it. + +Signed-off-by: Petr Lautrbach +--- + restorecond/restorecond.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/restorecond/restorecond.c b/restorecond/restorecond.c +index 36f82ae5e9cb..6353b3b72798 100644 +--- a/restorecond/restorecond.c ++++ b/restorecond/restorecond.c +@@ -217,6 +217,8 @@ int main(int argc, char **argv) + if (daemon(0, 0) < 0) + exitApp("daemon"); + write_pid_file(); ++ } else { ++ pidfile = 0; + } + + while (watch(master_fd, watch_file) == 0) { +-- +2.54.0 + diff --git a/changelog b/changelog index e9ad342..bb32e33 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,6 @@ +* Mon May 25 2026 Petr Lautrbach - 3.10-3 +- Several sandbox and seunshare security improvements + * Tue Apr 07 2026 Petr Lautrbach - 3.10-2 - restorecond.service: Use Type=simple diff --git a/policycoreutils.spec b/policycoreutils.spec index 5b49f91..aa6f33e 100644 --- a/policycoreutils.spec +++ b/policycoreutils.spec @@ -11,7 +11,7 @@ Summary: SELinux policy core utilities Name: policycoreutils Version: 3.10 -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 @@ -48,6 +48,22 @@ Patch0005: 0005-python-sepolicy-Fix-spec-file-dependencies.patch Patch0006: 0006-sepolicy-Fix-detection-of-writeable-locations.patch Patch0007: 0007-restorecond-Add-F-for-run-in-foreground.patch Patch0008: 0008-restorecond.service-Use-Type-simple.patch +Patch0009: 0009-seunshare-guard-fallible-function-calls-by-checking-.patch +Patch0010: 0010-sandbox-seunshare-pass-O_NOFOLLOW-to-openat.patch +Patch0011: 0011-sandbox-seunshare-switch-seunshare_mount_file-to-use.patch +Patch0012: 0012-sandbox-seunshare-fix-error-checking-for-setfsuid.patch +Patch0013: 0013-sandbox-seunshare-remount-tmp-and-var-tmp-with-the-p.patch +Patch0014: 0014-sandbox-seunshare-prevent-rsync-from-interpreting-pa.patch +Patch0015: 0015-sandbox-seunshare-fix-getopt-flags.patch +Patch0016: 0016-sandbox-seunshare-prevent-path-traversal-via-W-P.patch +Patch0017: 0017-sandbox-seunshare-verify-RUNTIME_DIR-before-use.patch +Patch0018: 0018-sandbox-seunshare-drop-unused-runuserdir_r.patch +Patch0019: 0019-sandbox-seunshare-fix-killall-realloc-and-missing-ty.patch +Patch0020: 0020-sandbox-seunshare-rewrite-to-pin-directories-before-.patch +Patch0021: 0021-sandbox-seunshare-fully-check-setfsuid-calls.patch +Patch0022: 0022-sandbox-seunshare-check-owner-in-seunshare_mount_fil.patch +Patch0023: 0023-sandbox-seunshare-fix-fd_tmpdir_r-check.patch +Patch0024: 0024-restorecond-Do-not-unlink-pidfile-if-not-used.patch # Patch list end # gen_changelog