policycoreutils-3.10-3

- Several sandbox and seunshare security improvements
Resolves: RHEL-175829
This commit is contained in:
Petr Lautrbach 2026-05-25 09:33:18 +02:00
parent d5da37c390
commit ef4edd927a
18 changed files with 1539 additions and 1 deletions

View File

@ -0,0 +1,63 @@
From 0edfc7cd42fdded9c697370d6932eecad2aa7449 Mon Sep 17 00:00:00 2001
From: Rahul Sandhu <nvraxn@posteo.uk>
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 <nvraxn@posteo.uk>
Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>
---
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

View File

@ -0,0 +1,30 @@
From 8d2c7360f5749b3ccacd348c495399f24318d7b4 Mon Sep 17 00:00:00 2001
From: Stephen Smalley <stephen.smalley.work@gmail.com>
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 <jwcart2@gmail.com>
Signed-off-by: Stephen Smalley <stephen.smalley.work@gmail.com>
---
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

View File

@ -0,0 +1,52 @@
From e964a574c2ca8ec1bcd3172dc185ada8044dceee Mon Sep 17 00:00:00 2001
From: Stephen Smalley <stephen.smalley.work@gmail.com>
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 <stephen.smalley.work@gmail.com>
Acked-by: Petr Lautrbach <lautrbach@redhat.com>
---
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

View File

@ -0,0 +1,33 @@
From 9c5320c6d4a1eb91b57e4e12737882ef131c2faa Mon Sep 17 00:00:00 2001
From: Stephen Smalley <stephen.smalley.work@gmail.com>
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 <stephen.smalley.work@gmail.com>
Acked-by: Petr Lautrbach <lautrbach@redhat.com>
---
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

View File

@ -0,0 +1,86 @@
From f78708ea5d6d3d6da4fa20527b459fa55075d8a6 Mon Sep 17 00:00:00 2001
From: Stephen Smalley <stephen.smalley.work@gmail.com>
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 <stephen.smalley.work@gmail.com>
Acked-by: Petr Lautrbach <lautrbach@redhat.com>
---
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

View File

@ -0,0 +1,75 @@
From dd620e8af44e1cb59d70837e80a5d55a8d3e789f Mon Sep 17 00:00:00 2001
From: Stephen Smalley <stephen.smalley.work@gmail.com>
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 <stephen.smalley.work@gmail.com>
---
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 + <glob list> + dst + NULL */
- *cmd = calloc(2 + fglob.gl_pathc + 2, sizeof(char *));
+ /* rsync -trlHDq -- <glob list> 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

View File

@ -0,0 +1,44 @@
From 9c29653dc6b0f4e9a74cd119d60a84ed8c4a6ec6 Mon Sep 17 00:00:00 2001
From: Stephen Smalley <stephen.smalley.work@gmail.com>
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 <stephen.smalley.work@gmail.com>
---
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

View File

@ -0,0 +1,47 @@
From 5da4d90480164c13a5bc8f8c820e701790499ecc Mon Sep 17 00:00:00 2001
From: Stephen Smalley <stephen.smalley.work@gmail.com>
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 <stephen.smalley.work@gmail.com>
---
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

View File

@ -0,0 +1,38 @@
From cceeb1def62ef78fed802684e9500cfa1aefa6b3 Mon Sep 17 00:00:00 2001
From: Stephen Smalley <stephen.smalley.work@gmail.com>
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 <stephen.smalley.work@gmail.com>
---
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

View File

@ -0,0 +1,48 @@
From c2f54aff134bcba8ae9701d26d7711530d72be76 Mon Sep 17 00:00:00 2001
From: Stephen Smalley <stephen.smalley.work@gmail.com>
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 <stephen.smalley.work@gmail.com>
---
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

View File

@ -0,0 +1,95 @@
From 47a8bd5d29455f1b8496a2bc7ba95f3f535c2ba7 Mon Sep 17 00:00:00 2001
From: Stephen Smalley <stephen.smalley.work@gmail.com>
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 <stephen.smalley.work@gmail.com>
---
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

View File

@ -0,0 +1,606 @@
From 9c44bb929a7937897a787790ece679464345c5d9 Mon Sep 17 00:00:00 2001
From: Stephen Smalley <stephen.smalley.work@gmail.com>
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 <stephen.smalley.work@gmail.com>
---
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

View File

@ -0,0 +1,158 @@
From 59cfed3bf23511f792bde3085759ab44ed643a4a Mon Sep 17 00:00:00 2001
From: Stephen Smalley <stephen.smalley.work@gmail.com>
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 <stephen.smalley.work@gmail.com>
---
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

View File

@ -0,0 +1,85 @@
From 8a3de70622d252f1ee070d6d9e9b019b9a6edd08 Mon Sep 17 00:00:00 2001
From: Stephen Smalley <stephen.smalley.work@gmail.com>
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 <stephen.smalley.work@gmail.com>
---
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

View File

@ -0,0 +1,29 @@
From d99b5a313a2790b1ca703023112f548cbe65617a Mon Sep 17 00:00:00 2001
From: Stephen Smalley <stephen.smalley.work@gmail.com>
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 <stephen.smalley.work@gmail.com>
---
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

View File

@ -0,0 +1,30 @@
From a063bc28044a23dd7aa0c496c52a9c9ff4abd349 Mon Sep 17 00:00:00 2001
From: Petr Lautrbach <lautrbach@redhat.com>
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 <lautrbach@redhat.com>
---
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

View File

@ -1,3 +1,6 @@
* Mon May 25 2026 Petr Lautrbach <lautrbach@redhat.com> - 3.10-3
- Several sandbox and seunshare security improvements
* Tue Apr 07 2026 Petr Lautrbach <lautrbach@redhat.com> - 3.10-2
- restorecond.service: Use Type=simple

View File

@ -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