diff --git a/SOURCES/0026-semanage-improve-e-documentation-and-fix-delete-oper.patch b/SOURCES/0026-semanage-improve-e-documentation-and-fix-delete-oper.patch new file mode 100644 index 0000000..56bc53e --- /dev/null +++ b/SOURCES/0026-semanage-improve-e-documentation-and-fix-delete-oper.patch @@ -0,0 +1,92 @@ +From 17b468428b8a30476d6add39e8a83ff550005968 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= +Date: Tue, 14 Jan 2025 16:52:28 +0100 +Subject: [PATCH] semanage: improve -e documentation and fix delete operation +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Content-type: text/plain + +Improve the documentation around the -e/--equal option for semanage +fcontext. + +Closes: https://github.com/SELinuxProject/selinux/issues/457 +Signed-off-by: Christian Göttsche +Acked-by: Petr Lautrbach +--- + python/semanage/semanage | 13 +++++++------ + python/semanage/semanage-fcontext.8 | 6 +++--- + 2 files changed, 10 insertions(+), 9 deletions(-) + +diff --git a/python/semanage/semanage b/python/semanage/semanage +index b269b9fca65b..891662699afd 100644 +--- a/python/semanage/semanage ++++ b/python/semanage/semanage +@@ -54,7 +54,7 @@ usage_login = "semanage login [-h] [-n] [-N] [-S STORE] [" + usage_login_dict = {' --add': ('-s SEUSER', '-r RANGE', 'LOGIN',), ' --modify': ('-s SEUSER', '-r RANGE', 'LOGIN',), ' --delete': ('LOGIN',), ' --list': ('-C',), ' --extract': ('',), ' --deleteall': ('',)} + + usage_fcontext = "semanage fcontext [-h] [-n] [-N] [-S STORE] [" +-usage_fcontext_dict = {' --add': ('(', '-t TYPE', '-f FTYPE', '-r RANGE', '-s SEUSER', '|', '-e EQUAL', ')', 'FILE_SPEC',), ' --delete': ('(', '-t TYPE', '-f FTYPE', '|', '-e EQUAL', ')', 'FILE_SPEC',), ' --modify': ('(', '-t TYPE', '-f FTYPE', '-r RANGE', '-s SEUSER', '|', '-e EQUAL', ')', 'FILE_SPEC',), ' --list': ('[-C]',), ' --extract': ('',), ' --deleteall': ('',)} ++usage_fcontext_dict = {' --add': ('(', '-t TYPE', '-f FTYPE', '-r RANGE', '-s SEUSER', '|', '-e TARGET_PATH', ')', 'FILE_SPEC',), ' --delete': ('(', '-t TYPE', '-f FTYPE', '|', '-e TARGET_PATH', ')', 'FILE_SPEC',), ' --modify': ('(', '-t TYPE', '-f FTYPE', '-r RANGE', '-s SEUSER', '|', '-e TARGET_PATH', ')', 'FILE_SPEC',), ' --list': ('[-C]',), ' --extract': ('',), ' --deleteall': ('',)} + + usage_user = "semanage user [-h] [-n] [-N] [-S STORE] [" + usage_user_dict = {' --add': ('(', '-L LEVEL', '-R ROLES', '-r RANGE', 'SEUSER', ')'), ' --delete': ('SEUSER',), ' --modify': ('(', '-L LEVEL', '-R ROLES', '-r RANGE', '-s SEUSER', 'SEUSER', ')'), ' --list': ('-C',), ' --extract': ('',), ' --deleteall': ('',)} +@@ -306,7 +306,7 @@ def setupLoginParser(subparsers): + def handleFcontext(args): + fcontext_args = {'list': [('equal', 'ftype', 'seuser', 'type'), ('')], 'add': [('locallist'), ('type', 'file_spec')], 'modify': [('locallist'), ('type', 'file_spec')], 'delete': [('locallist'), ('file_spec')], 'extract': [('locallist', 'equal', 'ftype', 'seuser', 'type'), ('')], 'deleteall': [('locallist'), ('')]} + # we can not use mutually for equal because we can define some actions together with equal +- fcontext_equal_args = {'equal': [('list', 'locallist', 'type', 'ftype', 'seuser', 'deleteall', 'extract'), ()]} ++ fcontext_equal_args = {'equal': [('list', 'locallist', 'type', 'ftype', 'seuser', 'deleteall', 'extract'), ('file_spec')]} + + if args.action and args.equal: + handle_opts(args, fcontext_equal_args, "equal") +@@ -355,9 +355,10 @@ def setupFcontextParser(subparsers): + parser_add_extract(fcontext_action, "fcontext") + parser_add_deleteall(fcontext_action, "fcontext") + +- fcontextParser.add_argument('-e', '--equal', help=_( +- 'Substitute target path with sourcepath when generating default label. This is used with fcontext. Requires source and target \ +-path arguments. The context labeling for the target subtree is made equivalent to that defined for the source.' ++ fcontextParser.add_argument('-e', '--equal', metavar='TARGET_PATH', help=_( ++ 'Substitute FILE_SPEC with TARGET_PATH for file label lookup. This is used with fcontext. Requires source and target \ ++path arguments to be path prefixes and does not support regular expressions. \ ++The context labeling for the target subtree is made equivalent to that defined for the source.' + )) + fcontextParser.add_argument('-f', '--ftype', default="", choices=["a", "f", "d", "c", "b", "s", "l", "p"], help=_( + 'File Type. This is used with fcontext. Requires a file type as shown in the mode field by ls, e.g. use d to match only \ +@@ -368,7 +369,7 @@ If you do not specify a file type, the file type will default to "all files".' + parser_add_seuser(fcontextParser, "fcontext") + parser_add_type(fcontextParser, "fcontext") + parser_add_range(fcontextParser, "fcontext") +- fcontextParser.add_argument('file_spec', nargs='?', default=None, help=_('Path to be labeled (may be in the form of a Perl compatible regular expression)')) ++ fcontextParser.add_argument('file_spec', nargs='?', default=None, metavar='FILE_SPEC', help=_('Path to be labeled (may be in the form of a Perl compatible regular expression)')) + fcontextParser.set_defaults(func=handleFcontext) + + +diff --git a/python/semanage/semanage-fcontext.8 b/python/semanage/semanage-fcontext.8 +index 3e327d88d146..3a96c62fda5d 100644 +--- a/python/semanage/semanage-fcontext.8 ++++ b/python/semanage/semanage-fcontext.8 +@@ -3,7 +3,7 @@ + semanage\-fcontext \- SELinux Policy Management file context tool + + .SH "SYNOPSIS" +-.B semanage fcontext [\-h] [\-n] [\-N] [\-S STORE] [ \-\-add ( \-t TYPE \-f FTYPE \-r RANGE \-s SEUSER | \-e EQUAL ) FILE_SPEC | \-\-delete ( \-t TYPE \-f FTYPE | \-e EQUAL ) FILE_SPEC | \-\-deleteall | \-\-extract | \-\-list [\-C] | \-\-modify ( \-t TYPE \-f FTYPE \-r RANGE \-s SEUSER | \-e EQUAL ) FILE_SPEC ] ++.B semanage fcontext [\-h] [\-n] [\-N] [\-S STORE] [ \-\-add ( \-t TYPE \-f FTYPE \-r RANGE \-s SEUSER | \-e TARGET_PATH ) FILE_SPEC | \-\-delete ( \-t TYPE \-f FTYPE | \-e TARGET_PATH ) FILE_SPEC | \-\-deleteall | \-\-extract | \-\-list [\-C] | \-\-modify ( \-t TYPE \-f FTYPE \-r RANGE \-s SEUSER | \-e TARGET_PATH ) FILE_SPEC ] + + .SH "DESCRIPTION" + semanage is used to configure certain elements of +@@ -66,8 +66,8 @@ Extract customizable commands, for use within a transaction + .I \-D, \-\-deleteall + Remove all local customizations + .TP +-.I \-e EQUAL, \-\-equal EQUAL +-Substitute target path with sourcepath when generating default label. This is used with fcontext. Requires source and target path arguments. The context labeling for the target subtree is made equivalent to that defined for the source. ++.I \-e TARGET_PATH, \-\-equal TARGET_PATH ++Substitute FILE_SPEC with TARGET_PATH for file label lookup. This is used with fcontext. Requires source and target path arguments to be path prefixes and does not support regular expressions. The context labeling for the target subtree is made equivalent to that defined for the source. + .TP + .I \-f [{a,f,d,c,b,s,l,p}], \-\-ftype [{a,f,d,c,b,s,l,p}] + File Type. This is used with fcontext. Requires a file type as shown in the mode field by ls, e.g. use 'd' to match only directories or 'f' to match only regular files. The following file type options can be passed: f (regular file),d (directory),c (character device), b (block device),s (socket),l (symbolic link),p (named pipe). If you do not specify a file type, the file type will default to "all files". +-- +2.53.0 + diff --git a/SOURCES/0027-improve-semanage-man-pages-Add-examples-for-r-RANGE-.patch b/SOURCES/0027-improve-semanage-man-pages-Add-examples-for-r-RANGE-.patch new file mode 100644 index 0000000..450f314 --- /dev/null +++ b/SOURCES/0027-improve-semanage-man-pages-Add-examples-for-r-RANGE-.patch @@ -0,0 +1,66 @@ +From eb250ffcf89287560ce1b1cda94ca429828ce645 Mon Sep 17 00:00:00 2001 +From: Pranav Lawate +Date: Tue, 7 Oct 2025 20:36:13 +0530 +Subject: [PATCH] improve semanage man pages: Add examples for -r RANGE flag + usage +Content-type: text/plain + +This patch adds missing examples to the semanage-port and +semanage-fcontext man pages showing the correct usage of the -r RANGE +flag for MLS/MCS systems. Currently, users who try to use the -r flag +without proper examples often encounter unclear error messages when +they provide invalid range formats. + +For example, here is a command with wrong range string value: +libsepol.mls_from_string: invalid MLS context s0.c0 (No such file or directory) +[...error output...] + +The added examples demonstrate: + - Correct MLS range format: s0:c0.c255 + - Complete command syntax with the -r flag for both port and + fcontext operations + - Clear indication that this is for MLS/MCS systems only + - Verification method using seinfo for port changes (semanage port -l + only shows type, not MLS range) + - Use -F flag to restorecon in fcontext example (required to force + relabeling) + +Signed-off-by: Pranav Lawate +Acked-by: Stephen Smalley +--- + python/semanage/semanage-fcontext.8 | 4 ++++ + python/semanage/semanage-port.8 | 3 +++ + 2 files changed, 7 insertions(+) + +diff --git a/python/semanage/semanage-fcontext.8 b/python/semanage/semanage-fcontext.8 +index 3a96c62fda5d..a29b74994763 100644 +--- a/python/semanage/semanage-fcontext.8 ++++ b/python/semanage/semanage-fcontext.8 +@@ -100,6 +100,10 @@ execute the following commands. + # semanage fcontext \-a \-e /home /disk6/home + # restorecon \-R \-v /disk6 + ++Add file-context with MLS range s0:c0.c255 for /secure directory (MLS/MCS systems only) ++# semanage fcontext \-a \-t admin_home_t \-r s0:c0.c255 "/secure(/.*)?" ++# restorecon \-R \-F \-v /secure ++ + .SH "SEE ALSO" + .BR selinux (8), + .BR semanage (8), +diff --git a/python/semanage/semanage-port.8 b/python/semanage/semanage-port.8 +index c6048660ca21..0df442901378 100644 +--- a/python/semanage/semanage-port.8 ++++ b/python/semanage/semanage-port.8 +@@ -61,6 +61,9 @@ Allow Apache to listen on tcp port 81 (i.e. assign tcp port 81 label http_port_t + # semanage port \-a \-t http_port_t \-p tcp 81 + Allow sshd to listen on tcp port 8991 (i.e. assign tcp port 8991 label ssh_port_t, which sshd is allowed to listen on) + # semanage port \-a \-t ssh_port_t \-p tcp 8991 ++Add a custom port 9999 with MLS range s0:c0.c255 (MLS/MCS systems only). Verify with seinfo. ++# semanage port \-a \-t http_port_t \-p tcp \-r s0:c0.c255 9999 ++# seinfo \-\-portcon \-x | grep 9999 + + .SH "SEE ALSO" + .BR selinux (8), +-- +2.53.0 + diff --git a/SOURCES/0028-semanage-fcontext-8-improve-e-documentation.patch b/SOURCES/0028-semanage-fcontext-8-improve-e-documentation.patch new file mode 100644 index 0000000..f2624f2 --- /dev/null +++ b/SOURCES/0028-semanage-fcontext-8-improve-e-documentation.patch @@ -0,0 +1,41 @@ +From fb10d6d66a4c93dda660f4c221646fbd0a0dca3f Mon Sep 17 00:00:00 2001 +From: Petr Lautrbach +Date: Tue, 24 Feb 2026 11:36:41 +0100 +Subject: [PATCH] semanage-fcontext(8): improve -e documentation +Content-type: text/plain + +Document that substitution based on another substitution does not work +in semanage-fcontext(8) man page + +Signed-off-by: Petr Lautrbach +--- + python/semanage/semanage-fcontext.8 | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/python/semanage/semanage-fcontext.8 b/python/semanage/semanage-fcontext.8 +index a29b74994763..b037491e3394 100644 +--- a/python/semanage/semanage-fcontext.8 ++++ b/python/semanage/semanage-fcontext.8 +@@ -67,7 +67,7 @@ Extract customizable commands, for use within a transaction + Remove all local customizations + .TP + .I \-e TARGET_PATH, \-\-equal TARGET_PATH +-Substitute FILE_SPEC with TARGET_PATH for file label lookup. This is used with fcontext. Requires source and target path arguments to be path prefixes and does not support regular expressions. The context labeling for the target subtree is made equivalent to that defined for the source. ++Substitute FILE_SPEC with TARGET_PATH for file label lookup. This is used with fcontext. Requires source and target path arguments to be path prefixes and does not support regular expressions. The context labeling for the target subtree is made equivalent to that defined for the source. Note that substitution based on another substitution does not work. + .TP + .I \-f [{a,f,d,c,b,s,l,p}], \-\-ftype [{a,f,d,c,b,s,l,p}] + File Type. This is used with fcontext. Requires a file type as shown in the mode field by ls, e.g. use 'd' to match only directories or 'f' to match only regular files. The following file type options can be passed: f (regular file),d (directory),c (character device), b (block device),s (socket),l (symbolic link),p (named pipe). If you do not specify a file type, the file type will default to "all files". +@@ -94,6 +94,10 @@ i.e. label everything under /home1 the same way /home is labeled + # semanage fcontext \-a \-e /home /home1 + # restorecon \-R \-v /home1 + ++If you substitute /home2 with /home1 from the example above it will not work. You must use direct substitution ++# semanage fcontext \-a \-e /home /home2 ++# restorecon \-R \-v /home2 ++ + For home directories under top level directory, for example /disk6/home, + execute the following commands. + # semanage fcontext \-a \-t home_root_t "/disk6" +-- +2.53.0 + diff --git a/SOURCES/0029-restorecond-Add-F-for-run-in-foreground.patch b/SOURCES/0029-restorecond-Add-F-for-run-in-foreground.patch new file mode 100644 index 0000000..7d2ae96 --- /dev/null +++ b/SOURCES/0029-restorecond-Add-F-for-run-in-foreground.patch @@ -0,0 +1,94 @@ +From 7e91c46817f113898d79326cf7b3ba8228894e42 Mon Sep 17 00:00:00 2001 +From: Petr Lautrbach +Date: Thu, 2 Apr 2026 17:17:12 +0200 +Subject: [PATCH] restorecond: Add -F for run in foreground +Content-type: text/plain + +Signed-off-by: Petr Lautrbach +--- + restorecond/restorecond.8 | 5 ++++- + restorecond/restorecond.c | 13 ++++++++----- + 2 files changed, 12 insertions(+), 6 deletions(-) + +diff --git a/restorecond/restorecond.8 b/restorecond/restorecond.8 +index bf8ec87562f8..bb99b24a2d69 100644 +--- a/restorecond/restorecond.8 ++++ b/restorecond/restorecond.8 +@@ -3,7 +3,7 @@ + restorecond \- daemon that watches for file creation and then sets the default SELinux file context + + .SH "SYNOPSIS" +-.B restorecond [\-d] [-h] [\-f restorecond_file ] [\-u] [\-v] ++.B restorecond [\-d] [-h] [\-f restorecond_file ] [\-F] [\-u] [\-v] + .P + + .SH "DESCRIPTION" +@@ -26,6 +26,9 @@ Print usage statement. + .B \-f restorecond_file + Use alternative restorecond.conf file. + .TP ++.B \-F ++Run in foreground, do not become a daemon. ++.TP + .B \-u + Turns on user mode. Runs restorecond in the user session and reads /etc/selinux/restorecond_user.conf. Uses dbus to make sure only one restorecond is running per user session. + .TP +diff --git a/restorecond/restorecond.c b/restorecond/restorecond.c +index d5f70fc2e2c1..36f82ae5e9cb 100644 +--- a/restorecond/restorecond.c ++++ b/restorecond/restorecond.c +@@ -76,6 +76,7 @@ int debug_mode = 0; + int terminate = 0; + int master_wd = -1; + int run_as_user = 0; ++int foreground_mode = 0; + + static void done(void) { + watch_list_free(master_fd); +@@ -124,7 +125,7 @@ static void term_handler(int s __attribute__ ((unused))) + + static void usage(char *program) + { +- printf("%s [-d] [-f restorecond_file ] [-u] [-v] \n", program); ++ printf("%s [-d] [-f restorecond_file ] [-F] [-n] [-u] [-v] \n", program); + } + + void exitApp(const char *msg) +@@ -165,7 +166,7 @@ int main(int argc, char **argv) + sigaction(SIGTERM, &sa, NULL); + + atexit( done ); +- while ((opt = getopt(argc, argv, "hdf:uv")) > 0) { ++ while ((opt = getopt(argc, argv, "hdf:Fuv")) > 0) { + switch (opt) { + case 'd': + debug_mode = 1; +@@ -173,6 +174,9 @@ int main(int argc, char **argv) + case 'f': + watch_file = optarg; + break; ++ case 'F': ++ foreground_mode = 1; ++ break; + case 'u': + run_as_user = 1; + break; +@@ -209,13 +213,12 @@ int main(int argc, char **argv) + + read_config(master_fd, watch_file); + +- if (!debug_mode) { ++ if (!debug_mode && !foreground_mode) { + if (daemon(0, 0) < 0) + exitApp("daemon"); ++ write_pid_file(); + } + +- write_pid_file(); +- + while (watch(master_fd, watch_file) == 0) { + } + +-- +2.53.0 + diff --git a/SOURCES/0030-restorecond.service-Use-Type-simple.patch b/SOURCES/0030-restorecond.service-Use-Type-simple.patch new file mode 100644 index 0000000..2a92eee --- /dev/null +++ b/SOURCES/0030-restorecond.service-Use-Type-simple.patch @@ -0,0 +1,39 @@ +From dd4686ae541637671d1e2c0e8fc2f3605fd6dc4b Mon Sep 17 00:00:00 2001 +From: Petr Lautrbach +Date: Thu, 2 Apr 2026 17:19:05 +0200 +Subject: [PATCH] restorecond.service: Use Type=simple +Content-type: text/plain + +There's a race condition when daemon() in parent process immediately +exits while pid file is not created or updated. Using Type=forking it +can confuse systemd which tries to open non-existing pid file. It's +better to run restocond in foreground and use Type=simple + +Fixes: + + openat(80, "restorecond.pid", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_PATH) = -1 ENOENT (No such file or directory) <0.000006> + +Signed-off-by: Petr Lautrbach +--- + restorecond/restorecond.service | 5 ++--- + 1 file changed, 2 insertions(+), 3 deletions(-) + +diff --git a/restorecond/restorecond.service b/restorecond/restorecond.service +index 0e4ea72d08ae..56f970707d93 100644 +--- a/restorecond/restorecond.service ++++ b/restorecond/restorecond.service +@@ -5,9 +5,8 @@ ConditionPathExists=/etc/selinux/restorecond.conf + ConditionSecurity=selinux + + [Service] +-Type=forking +-ExecStart=/usr/sbin/restorecond +-PIDFile=/run/restorecond.pid ++Type=simple ++ExecStart=/usr/sbin/restorecond -F + + [Install] + WantedBy=multi-user.target +-- +2.53.0 + diff --git a/SOURCES/0031-sepolicy-Fix-detection-of-writeable-locations.patch b/SOURCES/0031-sepolicy-Fix-detection-of-writeable-locations.patch new file mode 100644 index 0000000..8db8f5e --- /dev/null +++ b/SOURCES/0031-sepolicy-Fix-detection-of-writeable-locations.patch @@ -0,0 +1,65 @@ +From c5dbf35de55fadcc404149b02aad219da5bd3c4c Mon Sep 17 00:00:00 2001 +From: Petr Lautrbach +Date: Mon, 5 May 2025 18:28:40 +0200 +Subject: [PATCH] sepolicy: Fix detection of writeable locations + +- update substitutions from /etc/dnf/var + +It seems that by default dnf api does not read /etc/dnf/var/* files to +update substitutions and so it can't resolve correct mirror list when a +variable is part of the link. + +- alway run query.run() + +Fixes: + Traceback (most recent call last): + File "/usr/lib/python3.12/site-packages/dnf/repo.py", line 574, in load + ret = self._repo.load() + ^^^^^^^^^^^^^^^^^ + File "/usr/lib64/python3.12/site-packages/libdnf/repo.py", line 467, in load + return _repo.Repo_load(self) + ^^^^^^^^^^^^^^^^^^^^^ + libdnf._error.Error: Failed to download metadata for repo 'baseos': + Cannot prepare internal mirrorlist: Status code: 404 for + https://mirrors.centos.org/metalink?repo=centos-baseos-$stream&arch=x86_64&protocol=https,http (IP:152.19.134.198) + +Signed-off-by: Petr Lautrbach +--- + python/sepolicy/sepolicy/generate.py | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/python/sepolicy/sepolicy/generate.py b/python/sepolicy/sepolicy/generate.py +index f726ad51..a0314a70 100644 +--- a/python/sepolicy/sepolicy/generate.py ++++ b/python/sepolicy/sepolicy/generate.py +@@ -1267,15 +1267,15 @@ allow %s_t %s_t:%s_socket name_%s; + import dnf + + with dnf.Base() as base: ++ base.conf.substitutions.update_from_etc('/') + base.read_all_repos() + base.fill_sack(load_system_repo=True) + + query = base.sack.query() + +- pq = query.available() +- pq = pq.filter(file=self.program) ++ pq = query.filter(file=self.program) + +- for pkg in pq: ++ for pkg in pq.run(): + self.rpms.append(pkg.name) + for fname in pkg.files: + for b in self.DEFAULT_DIRS: +@@ -1288,7 +1288,7 @@ allow %s_t %s_t:%s_socket name_%s; + self.add_dir(fname) + sq = query.available() + sq = sq.filter(provides=pkg.source_name) +- for bpkg in sq: ++ for bpkg in sq.run(): + for fname in bpkg.files: + for b in self.DEFAULT_DIRS: + if b == "/etc": +-- +2.53.0 + diff --git a/SOURCES/0032-seunshare-Add-P-pipewiresocket-W-waylandsocket-optio.patch b/SOURCES/0032-seunshare-Add-P-pipewiresocket-W-waylandsocket-optio.patch new file mode 100644 index 0000000..5f41417 --- /dev/null +++ b/SOURCES/0032-seunshare-Add-P-pipewiresocket-W-waylandsocket-optio.patch @@ -0,0 +1,232 @@ +From 78af7de720215c5697f4303e7317a104fe088bf4 Mon Sep 17 00:00:00 2001 +From: Petr Lautrbach +Date: Tue, 14 May 2024 12:56:50 +0200 +Subject: [PATCH] seunshare: Add [ -P pipewiresocket ] [ -W waylandsocket ] + options +Content-type: text/plain + +Mount /run/user/UID/ or /run/user/UID/ +inside unshared /run/user/UID directory + +Signed-off-by: Petr Lautrbach +--- + sandbox/seunshare.c | 120 +++++++++++++++++++++++++++++++++++++++++--- + 1 file changed, 113 insertions(+), 7 deletions(-) + +diff --git a/sandbox/seunshare.c b/sandbox/seunshare.c +index b49fc2cd8050..2512a18c7b52 100644 +--- a/sandbox/seunshare.c ++++ b/sandbox/seunshare.c +@@ -53,7 +53,8 @@ + + #define BUF_SIZE 1024 + #define DEFAULT_PATH "/usr/bin:/bin" +-#define USAGE_STRING _("USAGE: seunshare [ -v ] [ -C ] [ -k ] [ -t tmpdir ] [ -h homedir ] [ -r runuserdir ] [ -Z CONTEXT ] -- executable [args] ") ++#define USAGE_STRING _("USAGE: seunshare [ -v ] [ -C ] [ -k ] [ -t tmpdir ] [ -h homedir ] \ ++[ -r runuserdir ] [ -P pipewiresocket ] [ -W waylandsocket ] [ -Z CONTEXT ] -- executable [args] ") + + #define strdup_or_err(args, index, src) do { \ + args[index] = strdup(src); \ +@@ -270,6 +271,10 @@ static int seunshare_mount(const char *src, const char *dst, struct stat *src_st + is_tmp = 1; + } + ++ if (strncmp("/run/user", dst, 9) == 0) { ++ flags = flags | MS_REC; ++ } ++ + /* mount directory */ + if (mount(src, dst, NULL, MS_BIND | flags, NULL) < 0) { + fprintf(stderr, _("Failed to mount %s on %s: %s\n"), src, dst, strerror(errno)); +@@ -294,6 +299,31 @@ static int seunshare_mount(const char *src, const char *dst, struct stat *src_st + + } + ++/** ++ * Mount directory and check that we mounted the right directory. ++ */ ++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); ++ } ++ /* mount file */ ++ if (mount(src, dst, NULL, MS_BIND | flags, NULL) < 0) { ++ fprintf(stderr, _("Failed to mount %s on %s: %s\n"), src, dst, strerror(errno)); ++ return -1; ++ } ++ ++ return 0; ++ ++} ++ + /* + If path is empty or ends with "/." or "/.. return -1 else return 0; + */ +@@ -696,6 +726,8 @@ killall (const char *execcon) + int main(int argc, char **argv) { + int status = -1; + const char *execcon = NULL; ++ const char *pipewire_socket = NULL; ++ const char *wayland_display = NULL; + + int clflag; /* holds codes for command line flags */ + int kill_all = 0; +@@ -721,6 +753,8 @@ int main(int argc, char **argv) { + {"verbose", 1, 0, 'v'}, + {"context", 1, 0, 'Z'}, + {"capabilities", 1, 0, 'C'}, ++ {"wayland", 1, 0, 'W'}, ++ {"pipewire", 1, 0, 'P'}, + {NULL, 0, 0, 0} + }; + +@@ -750,7 +784,7 @@ int main(int argc, char **argv) { + } + + while (1) { +- clflag = getopt_long(argc, argv, "Ccvh:r:t:Z:", long_options, NULL); ++ clflag = getopt_long(argc, argv, "Ccvh:r:t:W:Z:", long_options, NULL); + if (clflag == -1) + break; + +@@ -773,6 +807,12 @@ int main(int argc, char **argv) { + case 'C': + cap_set = CAPNG_SELECT_CAPS; + break; ++ case 'P': ++ pipewire_socket = optarg; ++ break; ++ case 'W': ++ wayland_display = optarg; ++ break; + case 'Z': + execcon = optarg; + break; +@@ -847,8 +887,14 @@ int main(int argc, char **argv) { + char *display = NULL; + char *LANG = NULL; + char *RUNTIME_DIR = NULL; ++ 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 */ ++ + + if (unshare(CLONE_NEWNS) < 0) { + perror(_("Failed to unshare")); +@@ -885,6 +931,42 @@ int main(int argc, char **argv) { + } + } + ++ if ((XDG_SESSION_TYPE = getenv("XDG_SESSION_TYPE")) != NULL) { ++ if ((XDG_SESSION_TYPE = strdup(XDG_SESSION_TYPE)) == NULL) { ++ perror(_("Out of memory")); ++ goto childerr; ++ } ++ } ++ ++ 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")); ++ goto childerr; ++ } ++ ++ if (asprintf(&wayland_path, "%s/%s", RUNTIME_DIR, wayland_display) == -1) { ++ perror(_("Out of memory")); ++ goto childerr; ++ } ++ ++ if (seunshare_mount_file(wayland_path, wayland_path_s) == -1) ++ 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; ++ } ++ seunshare_mount_file(pipewire_path, pipewire_path_s); ++ } ++ } ++ + /* mount homedir, runuserdir and tmpdir, in this order */ + if (runuserdir_s && seunshare_mount(runuserdir_s, RUNTIME_DIR, + &st_runuserdir_s) != 0) goto childerr; +@@ -896,10 +978,21 @@ int main(int argc, char **argv) { + if (drop_privs(uid) != 0) goto childerr; + + /* construct a new environment */ +- if ((display = getenv("DISPLAY")) != NULL) { +- if ((display = strdup(display)) == NULL) { +- perror(_("Out of memory")); +- goto childerr; ++ ++ if (XDG_SESSION_TYPE && strcmp(XDG_SESSION_TYPE, "wayland") == 0) { ++ if (wayland_display == NULL && (wayland_display = getenv("WAYLAND_DISPLAY")) != NULL) { ++ if ((wayland_display = strdup(wayland_display)) == NULL) { ++ perror(_("Out of memory")); ++ goto childerr; ++ } ++ } ++ } ++ else { ++ if ((display = getenv("DISPLAY")) != NULL) { ++ if ((display = strdup(display)) == NULL) { ++ perror(_("Out of memory")); ++ goto childerr; ++ } + } + } + +@@ -915,8 +1008,16 @@ int main(int argc, char **argv) { + perror(_("Failed to clear environment")); + goto childerr; + } +- if (display) ++ if (display) { + rc |= setenv("DISPLAY", display, 1); ++ } ++ if (wayland_display) { ++ rc |= setenv("WAYLAND_DISPLAY", wayland_display, 1); ++ } ++ ++ if (XDG_SESSION_TYPE) ++ rc |= setenv("XDG_SESSION_TYPE", XDG_SESSION_TYPE, 1); ++ + if (LANG) + rc |= setenv("LANG", LANG, 1); + if (RUNTIME_DIR) +@@ -954,9 +1055,14 @@ int main(int argc, char **argv) { + fprintf(stderr, _("Failed to execute command %s: %s\n"), argv[optind], strerror(errno)); + childerr: + free(resolved_path); ++ free(wayland_path); ++ free(wayland_path_s); ++ free(pipewire_path); ++ free(pipewire_path_s); + free(display); + free(LANG); + free(RUNTIME_DIR); ++ free(XDG_SESSION_TYPE); + exit(-1); + } + +-- +2.55.0 + diff --git a/SOURCES/0033-seunshare-guard-fallible-function-calls-by-checking-.patch b/SOURCES/0033-seunshare-guard-fallible-function-calls-by-checking-.patch new file mode 100644 index 0000000..aac3b25 --- /dev/null +++ b/SOURCES/0033-seunshare-guard-fallible-function-calls-by-checking-.patch @@ -0,0 +1,63 @@ +From 53a21672ecff0f4c55956d7594194703c4e2bceb 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.55.0 + diff --git a/SOURCES/0034-sandbox-seunshare-pass-O_NOFOLLOW-to-openat.patch b/SOURCES/0034-sandbox-seunshare-pass-O_NOFOLLOW-to-openat.patch new file mode 100644 index 0000000..2bba409 --- /dev/null +++ b/SOURCES/0034-sandbox-seunshare-pass-O_NOFOLLOW-to-openat.patch @@ -0,0 +1,30 @@ +From 42b2ea57ba96d1e4f25d6b43f3ecac29e6780ff6 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.55.0 + diff --git a/SOURCES/0035-sandbox-seunshare-switch-seunshare_mount_file-to-use.patch b/SOURCES/0035-sandbox-seunshare-switch-seunshare_mount_file-to-use.patch new file mode 100644 index 0000000..ae6a70d --- /dev/null +++ b/SOURCES/0035-sandbox-seunshare-switch-seunshare_mount_file-to-use.patch @@ -0,0 +1,52 @@ +From 28fb4e58c3b51b55227596fb2b1d90a5a2f7fa1f 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.55.0 + diff --git a/SOURCES/0036-sandbox-seunshare-fix-error-checking-for-setfsuid.patch b/SOURCES/0036-sandbox-seunshare-fix-error-checking-for-setfsuid.patch new file mode 100644 index 0000000..5538f2f --- /dev/null +++ b/SOURCES/0036-sandbox-seunshare-fix-error-checking-for-setfsuid.patch @@ -0,0 +1,33 @@ +From db3b5ed011a2a675971e9416825f80359f97e4b8 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.55.0 + diff --git a/SOURCES/0037-sandbox-seunshare-remount-tmp-and-var-tmp-with-the-p.patch b/SOURCES/0037-sandbox-seunshare-remount-tmp-and-var-tmp-with-the-p.patch new file mode 100644 index 0000000..abe1930 --- /dev/null +++ b/SOURCES/0037-sandbox-seunshare-remount-tmp-and-var-tmp-with-the-p.patch @@ -0,0 +1,86 @@ +From d3734b8867f147e8aacd54f319fda357f7aaeed6 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.55.0 + diff --git a/SOURCES/0038-sandbox-seunshare-prevent-rsync-from-interpreting-pa.patch b/SOURCES/0038-sandbox-seunshare-prevent-rsync-from-interpreting-pa.patch new file mode 100644 index 0000000..41c2a36 --- /dev/null +++ b/SOURCES/0038-sandbox-seunshare-prevent-rsync-from-interpreting-pa.patch @@ -0,0 +1,75 @@ +From 245f370d7c2b039fe7e971d0a418650a94eea847 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.55.0 + diff --git a/SOURCES/0039-sandbox-seunshare-fix-getopt-flags.patch b/SOURCES/0039-sandbox-seunshare-fix-getopt-flags.patch new file mode 100644 index 0000000..0dae5f1 --- /dev/null +++ b/SOURCES/0039-sandbox-seunshare-fix-getopt-flags.patch @@ -0,0 +1,44 @@ +From 59013df444e0806d614b327ce5579ae4c1c7e285 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.55.0 + diff --git a/SOURCES/0040-sandbox-seunshare-prevent-path-traversal-via-W-P.patch b/SOURCES/0040-sandbox-seunshare-prevent-path-traversal-via-W-P.patch new file mode 100644 index 0000000..8511733 --- /dev/null +++ b/SOURCES/0040-sandbox-seunshare-prevent-path-traversal-via-W-P.patch @@ -0,0 +1,47 @@ +From d7335b75dccf033a56c34b99ee82d70c43057b5b 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.55.0 + diff --git a/SOURCES/0041-sandbox-seunshare-verify-RUNTIME_DIR-before-use.patch b/SOURCES/0041-sandbox-seunshare-verify-RUNTIME_DIR-before-use.patch new file mode 100644 index 0000000..bfa1249 --- /dev/null +++ b/SOURCES/0041-sandbox-seunshare-verify-RUNTIME_DIR-before-use.patch @@ -0,0 +1,38 @@ +From 89acb9dc3f7648b75583b0fb724fbfa00724ba08 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.55.0 + diff --git a/SOURCES/0042-sandbox-seunshare-drop-unused-runuserdir_r.patch b/SOURCES/0042-sandbox-seunshare-drop-unused-runuserdir_r.patch new file mode 100644 index 0000000..5994cb8 --- /dev/null +++ b/SOURCES/0042-sandbox-seunshare-drop-unused-runuserdir_r.patch @@ -0,0 +1,48 @@ +From 7ed6a7aef9c5aa878d0f4edd904585f52df0b265 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.55.0 + diff --git a/SOURCES/0043-sandbox-seunshare-fix-killall-realloc-and-missing-ty.patch b/SOURCES/0043-sandbox-seunshare-fix-killall-realloc-and-missing-ty.patch new file mode 100644 index 0000000..1b621fb --- /dev/null +++ b/SOURCES/0043-sandbox-seunshare-fix-killall-realloc-and-missing-ty.patch @@ -0,0 +1,95 @@ +From 549aa82c2ac5827283dcbecd697030889fa10be0 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.55.0 + diff --git a/SOURCES/0044-sandbox-seunshare-rewrite-to-pin-directories-before-.patch b/SOURCES/0044-sandbox-seunshare-rewrite-to-pin-directories-before-.patch new file mode 100644 index 0000000..01a38b1 --- /dev/null +++ b/SOURCES/0044-sandbox-seunshare-rewrite-to-pin-directories-before-.patch @@ -0,0 +1,606 @@ +From 9906f53d850fe4a1ec272bb90eaae952b9728023 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.55.0 + diff --git a/SOURCES/0045-sandbox-seunshare-fully-check-setfsuid-calls.patch b/SOURCES/0045-sandbox-seunshare-fully-check-setfsuid-calls.patch new file mode 100644 index 0000000..1b54bbc --- /dev/null +++ b/SOURCES/0045-sandbox-seunshare-fully-check-setfsuid-calls.patch @@ -0,0 +1,158 @@ +From 0f8787a80194d38746f2d7280c961882982fc688 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.55.0 + diff --git a/SOURCES/0046-sandbox-seunshare-check-owner-in-seunshare_mount_fil.patch b/SOURCES/0046-sandbox-seunshare-check-owner-in-seunshare_mount_fil.patch new file mode 100644 index 0000000..d7906a9 --- /dev/null +++ b/SOURCES/0046-sandbox-seunshare-check-owner-in-seunshare_mount_fil.patch @@ -0,0 +1,85 @@ +From 8bc3312574ea56d0895d0e15699197f4aa327cdd 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.55.0 + diff --git a/SOURCES/0047-sandbox-seunshare-fix-fd_tmpdir_r-check.patch b/SOURCES/0047-sandbox-seunshare-fix-fd_tmpdir_r-check.patch new file mode 100644 index 0000000..bf0fc4e --- /dev/null +++ b/SOURCES/0047-sandbox-seunshare-fix-fd_tmpdir_r-check.patch @@ -0,0 +1,29 @@ +From 730f8d1ceeabf00461773e01a6b19b14320bce24 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.55.0 + diff --git a/SOURCES/0048-sandbox-seunshare-check-for-errors-from-parent-drop_.patch b/SOURCES/0048-sandbox-seunshare-check-for-errors-from-parent-drop_.patch new file mode 100644 index 0000000..aa6f4f5 --- /dev/null +++ b/SOURCES/0048-sandbox-seunshare-check-for-errors-from-parent-drop_.patch @@ -0,0 +1,31 @@ +From 757801f1b140117729ebc0283d43893350733602 Mon Sep 17 00:00:00 2001 +From: Stephen Smalley +Date: Thu, 21 May 2026 11:31:33 -0400 +Subject: [PATCH] sandbox/seunshare: check for errors from parent drop_caps() +Content-type: text/plain + +Make sure we don't continue processing in the parent if +drop_caps() failed. + +Signed-off-by: Stephen Smalley +--- + sandbox/seunshare.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/sandbox/seunshare.c b/sandbox/seunshare.c +index 7a4233fbee00..1d13d2612f29 100644 +--- a/sandbox/seunshare.c ++++ b/sandbox/seunshare.c +@@ -1242,7 +1242,8 @@ childerr: + exit(-1); + } + +- drop_caps(); ++ if (drop_caps() == -1) ++ goto err; + + /* parent waits for child exit to do the cleanup */ + waitpid(child, &status, 0); +-- +2.55.0 + diff --git a/SOURCES/0049-sandbox-seunshare-fix-undefined-behavior-for-child.patch b/SOURCES/0049-sandbox-seunshare-fix-undefined-behavior-for-child.patch new file mode 100644 index 0000000..038204b --- /dev/null +++ b/SOURCES/0049-sandbox-seunshare-fix-undefined-behavior-for-child.patch @@ -0,0 +1,41 @@ +From 878680ecffa57f66894412a3cdbd0696ce414563 Mon Sep 17 00:00:00 2001 +From: Stephen Smalley +Date: Thu, 21 May 2026 11:45:26 -0400 +Subject: [PATCH] sandbox/seunshare: fix undefined behavior for child +Content-type: text/plain + +The global child variable is written in main() and read +by the SIGINT handler. To avoid undefined behavior, +declare child to be volatile sig_atomic_t so that it +is async-signal-safe. + +Signed-off-by: Stephen Smalley +--- + sandbox/seunshare.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/sandbox/seunshare.c b/sandbox/seunshare.c +index 1d13d2612f29..ac749d4ca69c 100644 +--- a/sandbox/seunshare.c ++++ b/sandbox/seunshare.c +@@ -63,7 +63,7 @@ + } while(0) + + static int verbose = 0; +-static int child = 0; ++static volatile sig_atomic_t child = 0; + + static capng_select_t cap_set = CAPNG_SELECT_CAPS; + +@@ -98,7 +98,7 @@ static int drop_privs(uid_t uid) + * If the user sends a siginto to seunshare, kill the child's session + */ + static void handler(int sig) { +- if (child > 0) kill(-child,sig); ++ if (child > 0) kill(-(pid_t)child,sig); + } + + /** +-- +2.55.0 + diff --git a/SOURCES/0050-sandbox-seunshare-drop-k-kill-support.patch b/SOURCES/0050-sandbox-seunshare-drop-k-kill-support.patch new file mode 100644 index 0000000..75384fa --- /dev/null +++ b/SOURCES/0050-sandbox-seunshare-drop-k-kill-support.patch @@ -0,0 +1,130 @@ +From 6ee3f8a59640483ec04cc74e44c7b88f631c700e Mon Sep 17 00:00:00 2001 +From: Stephen Smalley +Date: Thu, 21 May 2026 11:54:44 -0400 +Subject: [PATCH] sandbox/seunshare: drop -k/--kill support +Content-type: text/plain + +Drop the -k/--kill support; it is fundamentally racy, redundant +with killall -Z, and seemingly unused by sandbox itself. +Provide an error message to the user telling them how to achieve +the same effect via killall. + +Signed-off-by: Stephen Smalley +--- + sandbox/seunshare.c | 93 +-------------------------------------------- + 1 file changed, 1 insertion(+), 92 deletions(-) + +diff --git a/sandbox/seunshare.c b/sandbox/seunshare.c +index ac749d4ca69c..814d003d310f 100644 +--- a/sandbox/seunshare.c ++++ b/sandbox/seunshare.c +@@ -710,97 +710,6 @@ good: + return tmpdir; + } + +-#define PROC_BASE "/proc" +- +-static int +-killall (const char *execcon) +-{ +- DIR *dir; +- char *scon; +- struct dirent *de; +- pid_t *pid_table, pid, self; +- unsigned int i; +- unsigned int pids, max_pids; +- int running = 0; +- self = getpid(); +- if (!(dir = opendir(PROC_BASE))) { +- return -1; +- } +- max_pids = 256; +- pid_table = malloc(max_pids * sizeof (pid_t)); +- if (!pid_table) { +- (void)closedir(dir); +- return -1; +- } +- pids = 0; +- context_t con = context_new(execcon); +- if (!con) { +- free(pid_table); +- (void)closedir(dir); +- return -1; +- } +- const char *const mcs = context_range_get(con); +- const char *const type = context_type_get(con); +- if (!mcs || !type) { +- context_free(con); +- free(pid_table); +- (void)closedir(dir); +- return -1; +- } +- 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) { +- 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; +- } +- pid_table[pids++] = pid; +- } +- +- (void)closedir(dir); +- +- for (i = 0; i < pids; i++) { +- pid_t id = pid_table[i]; +- +- if (getpidcon(id, &scon) == 0) { +- +- 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 (pmcs && ptype && !strcmp(pmcs, mcs) && +- !strcmp(ptype, type)) +- kill(id, SIGKILL); +- +- context_free(pidcon); +- } +- freecon(scon); +- } +- running++; +- } +- +- context_free(con); +- free(pid_table); +- return running; +-} +- + int main(int argc, char **argv) { + int status = -1; + const char *execcon = NULL; +@@ -1253,7 +1162,7 @@ childerr: + kill(-child,SIGTERM); + + if (execcon && kill_all) +- killall(execcon); ++ fprintf(stderr, "-k/--kill no longer supported; run killall -Z %s\n", execcon); + + if (tmpdir_r) cleanup_tmpdir(tmpdir_r, tmpdir_s, pwd, 1); + +-- +2.55.0 + diff --git a/SPECS/policycoreutils.spec b/SPECS/policycoreutils.spec index e6706b9..ca6de18 100644 --- a/SPECS/policycoreutils.spec +++ b/SPECS/policycoreutils.spec @@ -11,7 +11,7 @@ Summary: SELinux policy core utilities Name: policycoreutils Version: 3.6 -Release: 5%{?dist} +Release: 9%{?dist} License: GPL-2.0-or-later # https://github.com/SELinuxProject/selinux/wiki/Releases Source0: https://github.com/SELinuxProject/selinux/releases/download/3.6/selinux-3.6.tar.gz @@ -61,6 +61,31 @@ Patch0022: 0022-semanage-Reset-active-value-when-deleting-boolean-cu.patch Patch0023: 0023-seunshare-always-use-translations-when-printing.patch Patch0024: 0024-seunshare-fix-the-frail-tmpdir-cleanup.patch Patch0025: 0025-sandbox-seunshare-Replace-system-with-execv-to-preve.patch +Patch0026: 0026-semanage-improve-e-documentation-and-fix-delete-oper.patch +Patch0027: 0027-improve-semanage-man-pages-Add-examples-for-r-RANGE-.patch +Patch0028: 0028-semanage-fcontext-8-improve-e-documentation.patch +Patch0029: 0029-restorecond-Add-F-for-run-in-foreground.patch +Patch0030: 0030-restorecond.service-Use-Type-simple.patch +Patch0031: 0031-sepolicy-Fix-detection-of-writeable-locations.patch +Patch0032: 0032-seunshare-Add-P-pipewiresocket-W-waylandsocket-optio.patch +Patch0033: 0033-seunshare-guard-fallible-function-calls-by-checking-.patch +Patch0034: 0034-sandbox-seunshare-pass-O_NOFOLLOW-to-openat.patch +Patch0035: 0035-sandbox-seunshare-switch-seunshare_mount_file-to-use.patch +Patch0036: 0036-sandbox-seunshare-fix-error-checking-for-setfsuid.patch +Patch0037: 0037-sandbox-seunshare-remount-tmp-and-var-tmp-with-the-p.patch +Patch0038: 0038-sandbox-seunshare-prevent-rsync-from-interpreting-pa.patch +Patch0039: 0039-sandbox-seunshare-fix-getopt-flags.patch +Patch0040: 0040-sandbox-seunshare-prevent-path-traversal-via-W-P.patch +Patch0041: 0041-sandbox-seunshare-verify-RUNTIME_DIR-before-use.patch +Patch0042: 0042-sandbox-seunshare-drop-unused-runuserdir_r.patch +Patch0043: 0043-sandbox-seunshare-fix-killall-realloc-and-missing-ty.patch +Patch0044: 0044-sandbox-seunshare-rewrite-to-pin-directories-before-.patch +Patch0045: 0045-sandbox-seunshare-fully-check-setfsuid-calls.patch +Patch0046: 0046-sandbox-seunshare-check-owner-in-seunshare_mount_fil.patch +Patch0047: 0047-sandbox-seunshare-fix-fd_tmpdir_r-check.patch +Patch0048: 0048-sandbox-seunshare-check-for-errors-from-parent-drop_.patch +Patch0049: 0049-sandbox-seunshare-fix-undefined-behavior-for-child.patch +Patch0050: 0050-sandbox-seunshare-drop-k-kill-support.patch # Patch list end Obsoletes: policycoreutils < 2.0.61-2 Conflicts: filesystem < 3, selinux-policy-base < 3.13.1-138 @@ -470,6 +495,19 @@ The policycoreutils-restorecond package contains the restorecond service. %systemd_postun_with_restart restorecond.service %changelog +* Tue Aug 04 2026 Petr Lautrbach - 3.6-9 +- Several sandbox and seunshare security improvements +- https://github.com/SELinuxProject/selinux/security/advisories/GHSA-h4m3-rc99-c7m5 + +* Fri Jul 17 2026 Vit Mojzis - 3.6-8 +- sepolicy: Fix detection of writeable locations (RHEL-141295) + +* Tue Apr 07 2026 Petr Lautrbach - 3.6-7 +- restorecond.service: Use Type=simple + +* Tue Feb 24 2026 Petr Lautrbach - 3.6-6 +- Improve semanage-fcontext(8) man page + * Tue Feb 03 2026 Petr Lautrbach - 3.6-5 - sandbox/seunshare: Replace system() with execv() to prevent shell injection - seunshare: fix the frail tmpdir cleanup