From: Stephen Smalley <stephen.smalley.work@gmail.com>
To: selinux@vger.kernel.org
Cc: Stephen Smalley <stephen.smalley.work@gmail.com>
Subject: [RFC PATCH selinuxns] libselinux: add selinux_unshare()
Date: Thu, 18 Sep 2025 09:51:19 -0400 [thread overview]
Message-ID: <20250918135118.9896-2-stephen.smalley.work@gmail.com> (raw)
RFC only, this demonstrates how to implement unsharing of the SELinux
namespace using the lsm_set_self_attr(LSM_UNSHARE_SELINUX...) system
call instead of the /sys/fs/selinux/unshare interface.
Provide a selinux_unshare() wrapper for
lsm_set_self_attr(LSM_ATTR_UNSHARE...) and other required processing
when unsharing the SELinux namespace.
Add a selinux_unshare() interface to unshare the SELinux namespace, and
a unshareselinux utility to run a shell or command in its own SELinux
and mount namespaces. The selinux_unshare() interface expects the caller
to have already unshared its mount namespace and created a
MS_REC|MS_PRIVATE mount of / prior to invoking it so that it can freely
modify the selinuxfs mount as needed by the unshare operation. The
unshareselinux utility demonstrates how to do this prior to calling
selinux_unshare(). Upon a successful return from selinux_unshare(),
the SELinux namespace will be unshared and there will be no selinuxfs
mount on /sys/fs/selinux. The caller can then proceed to mount a new
selinuxfs filesystem private to the new namespace, load a policy,
set enforcing mode, etc, as is commonly handled by init/systemd during
boot.
If/when this gets merged, the new selinux_unshare() symbol should
be moved to its own version in libselinux.map.
Signed-off-by: Stephen Smalley <stephen.smalley.work@gmail.com>
---
libselinux/include/selinux/selinux.h | 13 +++++
libselinux/src/libselinux.map | 1 +
libselinux/src/unshare.c | 71 ++++++++++++++++++++++++++++
libselinux/utils/.gitignore | 1 +
libselinux/utils/unshareselinux.c | 42 ++++++++++++++++
5 files changed, 128 insertions(+)
create mode 100644 libselinux/src/unshare.c
create mode 100644 libselinux/utils/unshareselinux.c
diff --git a/libselinux/include/selinux/selinux.h b/libselinux/include/selinux/selinux.h
index b1431e5d..1a68c29f 100644
--- a/libselinux/include/selinux/selinux.h
+++ b/libselinux/include/selinux/selinux.h
@@ -760,6 +760,19 @@ extern int selinux_lsetfilecon_default(const char *path);
*/
extern void selinux_reset_config(void);
+/*
+ * Unshare the SELinux namespace.
+ * Prior to invoking this API, the caller must have unshared the
+ * mount namespace (CLONE_NEWNS) and mounted a MS_REC|MS_PRIVATE
+ * / filesystem so that selinux_unshare() can freely modify any
+ * existing selinuxfs mount as needed for the unshare.
+ * Returns 0 on success, in which case the SELinux namespace has
+ * been unshared and any old selinuxfs mount will have been unmounted.
+ * The caller can then proceed to mount a new selinuxfs filesystem
+ * for the new namespace, load a policy, set enforcing mode, etc.
+ */
+extern int selinux_unshare(void);
+
#ifdef __cplusplus
}
#endif
diff --git a/libselinux/src/libselinux.map b/libselinux/src/libselinux.map
index ab002f01..f3966880 100644
--- a/libselinux/src/libselinux.map
+++ b/libselinux/src/libselinux.map
@@ -251,6 +251,7 @@ LIBSELINUX_3.5 {
global:
getpidprevcon;
getpidprevcon_raw;
+ selinux_unshare;
} LIBSELINUX_3.4;
LIBSELINUX_3.8 {
diff --git a/libselinux/src/unshare.c b/libselinux/src/unshare.c
new file mode 100644
index 00000000..d2fc7e20
--- /dev/null
+++ b/libselinux/src/unshare.c
@@ -0,0 +1,71 @@
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <sched.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/mount.h>
+#include <sys/vfs.h>
+#include <sys/statvfs.h>
+#include <sys/syscall.h>
+#include <linux/lsm.h>
+
+#include "selinux_internal.h"
+#include "policy.h"
+
+#ifndef LSM_ATTR_UNSHARE
+#define LSM_ATTR_UNSHARE 106
+#endif
+
+#ifndef __NR_lsm_set_self_attr
+#define __NR_lsm_set_self_attr 460
+#endif
+
+#ifndef HAVE_LSM_SET_SELF_ATTR
+#define HAVE_LSM_SET_SELF_ATTR 1
+static int lsm_set_self_attr(unsigned int attr, struct lsm_ctx *ctx,
+ uint32_t size, uint32_t flags)
+{
+ return syscall(__NR_lsm_set_self_attr, attr, ctx, size, flags);
+}
+#endif
+
+/*
+ * Precondition: caller must have already done unshare(CLONE_NEWNS) or
+ * been created via clone(CLONE_NEWNS) and mounted a MS_REC|MS_PRIVATE
+ * / filesystem so that any pre-existing selinuxfs mount can be
+ * modified freely by selinux_unshare(). See ../utils/unshareselinux.c
+ * for an example.
+ */
+int selinux_unshare(void)
+{
+ struct lsm_ctx ctx;
+ int ret;
+
+ ctx.id = LSM_ID_SELINUX;
+ ctx.flags = 0;
+ ctx.len = sizeof(ctx);
+ ctx.ctx_len = 0;
+
+ /* Unshare the SELinux namespace */
+ ret = lsm_set_self_attr(LSM_ATTR_UNSHARE, &ctx, sizeof(ctx), 0);
+ if (ret < 0)
+ return -1;
+
+ /* Unmount the selinuxfs which refers to the old/parent namespace */
+ ret = umount(SELINUXMNT);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Caller is responsible for mounting new selinuxfs, loading policy,
+ * setting enforcing mode, etc.
+ */
+
+ return 0;
+}
diff --git a/libselinux/utils/.gitignore b/libselinux/utils/.gitignore
index 2e10b14f..bb290d90 100644
--- a/libselinux/utils/.gitignore
+++ b/libselinux/utils/.gitignore
@@ -30,3 +30,4 @@ setfilecon
togglesebool
selinux_check_access
validatetrans
+unshareselinux
diff --git a/libselinux/utils/unshareselinux.c b/libselinux/utils/unshareselinux.c
new file mode 100644
index 00000000..b396b4fe
--- /dev/null
+++ b/libselinux/utils/unshareselinux.c
@@ -0,0 +1,42 @@
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/mount.h>
+#include <selinux/selinux.h>
+
+int main(int argc, char **argv)
+{
+ int ret;
+
+ ret = unshare(CLONE_NEWNS);
+ if (ret < 0) {
+ perror("unshare(CLONE_NEWNS)");
+ exit(1);
+ }
+
+ ret = mount("none", "/", NULL, MS_REC | MS_PRIVATE, NULL);
+ if (ret < 0) {
+ perror("mount(/)");
+ exit(1);
+ }
+
+ ret = selinux_unshare();
+ if (ret < 0) {
+ perror("selinux_unshare");
+ exit(1);
+ }
+
+ if (argc < 2) {
+ fprintf(stderr, "usage: %s command args...\n", argv[0]);
+ exit(1);
+ }
+
+ execvp(argv[1], &argv[1]);
+ perror(argv[1]);
+}
--
2.50.1
next reply other threads:[~2025-09-18 13:53 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-09-18 13:51 Stephen Smalley [this message]
2025-09-18 14:36 ` [RFC PATCH selinuxns] libselinux: add selinux_unshare() Stephen Smalley
2025-09-19 12:32 ` Stephen Smalley
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250918135118.9896-2-stephen.smalley.work@gmail.com \
--to=stephen.smalley.work@gmail.com \
--cc=selinux@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).