selinux.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [RFC PATCH v3] libselinux: add selinux_unshare() and is_selinux_unshared()
@ 2025-10-09 13:24 Stephen Smalley
  2025-10-09 13:52 ` Stephen Smalley
  2025-10-10 15:40 ` Christian Göttsche
  0 siblings, 2 replies; 3+ messages in thread
From: Stephen Smalley @ 2025-10-09 13:24 UTC (permalink / raw)
  To: selinux; +Cc: paul, john.johansen, casey, serge, Stephen Smalley

RFC only, this demonstrates how to implement unsharing of the SELinux
namespace using the lsm_set_self_attr(LSM_ATTR_UNSHARE, ...) system
call and how to implement detection of whether one is in an unshared
SELinux namespace that has not yet been fully initialized using
the lsm_get_self_attr(LSM_ATTR_UNSHARE, ...) system call.

Provide a selinux_unshare() wrapper for
lsm_set_self_attr(LSM_ATTR_UNSHARE, ...) and other required processing
when unsharing the SELinux namespace, and an is_selinux_unshared()
wrapper for lsm_get_self_attr(LSM_ATTR_UNSHARE, ...) for detecting
whether one is in an unshared SELinux namespace that has not yet
been fully initialized.

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.

Add an is_selinux_unshared() interface to detect whether one is in
an unshared SELinux namespace that has not yet been fully initialized
(i.e. no policy loaded yet), and a selinuxunshared utility to use
it from a shell or script.

Signed-off-by: Stephen Smalley <stephen.smalley.work@gmail.com>
---
v3 changes is_selinux_unshared() to return 0 if lsm_get_self_attr(2)
sets errno to ENOSYS or EOPNOTSUPP to allow callers to treat its
return value as a boolean and avoid needing to handle these cases
themselves.

 libselinux/include/selinux/selinux.h |  23 ++++++
 libselinux/src/libselinux.map        |   6 ++
 libselinux/src/unshare.c             | 117 +++++++++++++++++++++++++++
 libselinux/utils/.gitignore          |   2 +
 libselinux/utils/selinuxunshared.c   |  24 ++++++
 libselinux/utils/unshareselinux.c    |  42 ++++++++++
 6 files changed, 214 insertions(+)
 create mode 100644 libselinux/src/unshare.c
 create mode 100644 libselinux/utils/selinuxunshared.c
 create mode 100644 libselinux/utils/unshareselinux.c

diff --git a/libselinux/include/selinux/selinux.h b/libselinux/include/selinux/selinux.h
index b1431e5d..92186ddc 100644
--- a/libselinux/include/selinux/selinux.h
+++ b/libselinux/include/selinux/selinux.h
@@ -760,6 +760,29 @@ 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);
+
+/*
+ * Returns 1 if the SELinux namespace was unshared and has not
+ * yet been fully initialized (i.e. policy not yet loaded).
+ * Returns 0 if SELinux namespaces are not supported by the kernel,
+ * or the SELinux namespace was not unshared, or the namespace has
+ * been fully initialized already.
+ * Return < 0 on any error other than ENOSYS or EOPNOTSUPP.
+ */
+extern int is_selinux_unshared(void);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/libselinux/src/libselinux.map b/libselinux/src/libselinux.map
index ab002f01..77f5790c 100644
--- a/libselinux/src/libselinux.map
+++ b/libselinux/src/libselinux.map
@@ -262,3 +262,9 @@ LIBSELINUX_3.9 {
   global:
     context_to_str;
 } LIBSELINUX_3.8;
+
+LIBSELINUX_4.0 {
+  global:
+    selinux_unshare;
+    is_selinux_unshared;
+} LIBSELINUX_3.9;
diff --git a/libselinux/src/unshare.c b/libselinux/src/unshare.c
new file mode 100644
index 00000000..cd3ec78c
--- /dev/null
+++ b/libselinux/src/unshare.c
@@ -0,0 +1,117 @@
+#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_get_self_attr
+#define __NR_lsm_get_self_attr 459
+#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
+
+#ifndef HAVE_LSM_GET_SELF_ATTR
+#define HAVE_LSM_GET_SELF_ATTR 1
+static int lsm_get_self_attr(unsigned int attr, struct lsm_ctx *ctx,
+			     uint32_t *size, uint32_t flags)
+{
+	return syscall(__NR_lsm_get_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;
+}
+
+struct selinux_ctx {
+	struct lsm_ctx lsmctx;
+	char unshared;
+};
+
+/*
+ * Returns 1 if the SELinux namespace was unshared and has not
+ * yet been fully initialized (i.e. policy not yet loaded).
+ * Returns 0 if SELinux namespaces are not supported by the kernel,
+ * or the SELinux namespace was not unshared, or the namespace has
+ * been fully initialized already.
+ * Return < 0 on any error other than ENOSYS or EOPNOTSUPP.
+ */
+int is_selinux_unshared(void)
+{
+	struct selinux_ctx ctx;
+	uint32_t size = sizeof(ctx);
+	int ret;
+
+	ctx.lsmctx.id = LSM_ID_SELINUX;
+	ctx.lsmctx.flags = 0;
+	ctx.lsmctx.len = sizeof(ctx);
+	ctx.lsmctx.ctx_len = 0;
+
+	ret = lsm_get_self_attr(LSM_ATTR_UNSHARE, (struct lsm_ctx *)&ctx,
+				&size, LSM_FLAG_SINGLE);
+	if (ret < 0 && (errno == ENOSYS || errno == EOPNOTSUPP))
+		return 0;
+	if (ret < 0)
+		return ret;
+	return ctx.unshared;
+}
diff --git a/libselinux/utils/.gitignore b/libselinux/utils/.gitignore
index 2e10b14f..ffd2bc8e 100644
--- a/libselinux/utils/.gitignore
+++ b/libselinux/utils/.gitignore
@@ -30,3 +30,5 @@ setfilecon
 togglesebool
 selinux_check_access
 validatetrans
+unshareselinux
+selinuxunshared
diff --git a/libselinux/utils/selinuxunshared.c b/libselinux/utils/selinuxunshared.c
new file mode 100644
index 00000000..965f7ba3
--- /dev/null
+++ b/libselinux/utils/selinuxunshared.c
@@ -0,0 +1,24 @@
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <selinux/selinux.h>
+
+int main(int argc, char **argv)
+{
+	int ret;
+
+	if (argc != 1) {
+		fprintf(stderr, "usage: %s\n", argv[0]);
+		exit(-1);
+	}
+
+	ret = is_selinux_unshared();
+	if (ret < 0) {
+		perror(argv[0]);
+		exit(-1);
+	}
+
+	printf("%d\n", ret);
+
+	exit(!ret);
+}
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.51.0


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [RFC PATCH v3] libselinux: add selinux_unshare() and is_selinux_unshared()
  2025-10-09 13:24 [RFC PATCH v3] libselinux: add selinux_unshare() and is_selinux_unshared() Stephen Smalley
@ 2025-10-09 13:52 ` Stephen Smalley
  2025-10-10 15:40 ` Christian Göttsche
  1 sibling, 0 replies; 3+ messages in thread
From: Stephen Smalley @ 2025-10-09 13:52 UTC (permalink / raw)
  To: selinux; +Cc: paul, john.johansen, casey, serge

On Thu, Oct 9, 2025 at 9:28 AM Stephen Smalley
<stephen.smalley.work@gmail.com> wrote:
>
> RFC only, this demonstrates how to implement unsharing of the SELinux
> namespace using the lsm_set_self_attr(LSM_ATTR_UNSHARE, ...) system
> call and how to implement detection of whether one is in an unshared
> SELinux namespace that has not yet been fully initialized using
> the lsm_get_self_attr(LSM_ATTR_UNSHARE, ...) system call.
>
> Provide a selinux_unshare() wrapper for
> lsm_set_self_attr(LSM_ATTR_UNSHARE, ...) and other required processing
> when unsharing the SELinux namespace, and an is_selinux_unshared()
> wrapper for lsm_get_self_attr(LSM_ATTR_UNSHARE, ...) for detecting
> whether one is in an unshared SELinux namespace that has not yet
> been fully initialized.
>
> 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.
>
> Add an is_selinux_unshared() interface to detect whether one is in
> an unshared SELinux namespace that has not yet been fully initialized
> (i.e. no policy loaded yet), and a selinuxunshared utility to use
> it from a shell or script.
>
> Signed-off-by: Stephen Smalley <stephen.smalley.work@gmail.com>
> ---
> v3 changes is_selinux_unshared() to return 0 if lsm_get_self_attr(2)
> sets errno to ENOSYS or EOPNOTSUPP to allow callers to treat its
> return value as a boolean and avoid needing to handle these cases
> themselves.

For LSM maintainers not on the original distribution, here are the
corresponding patches for systemd that use these APIs:
https://lore.kernel.org/selinux/20251006143052.271761-2-stephen.smalley.work@gmail.com/T/#t

WRT the is_selinux_unshared() -> lsm_get_self_attr(LSM_ATTR_UNSHARE,
...), I originally tried to use it here:
https://lore.kernel.org/selinux/CAEjxPJ7zHgiSnd8jTGvBQm=Z=Qr645poO51f1N5shMhCLnXVJg@mail.gmail.com/T/#u
but that failed because systemd-nspawn sets a seccomp allow-list that
doesn't include the lsm_[gs]et_self_attr(2) system calls.

I could of course add them to its allow-list but it isn't strictly
required for this purpose since I can detect that I am in a SELinux
namespace using the environment variable approach instead.

This is one downside of the system call-based interface versus the
selinuxfs and proc-based interfaces; any container or sandboxing
runtime that sets an allow-list will need updating to permit access to
the new interfaces.

>
>  libselinux/include/selinux/selinux.h |  23 ++++++
>  libselinux/src/libselinux.map        |   6 ++
>  libselinux/src/unshare.c             | 117 +++++++++++++++++++++++++++
>  libselinux/utils/.gitignore          |   2 +
>  libselinux/utils/selinuxunshared.c   |  24 ++++++
>  libselinux/utils/unshareselinux.c    |  42 ++++++++++
>  6 files changed, 214 insertions(+)
>  create mode 100644 libselinux/src/unshare.c
>  create mode 100644 libselinux/utils/selinuxunshared.c
>  create mode 100644 libselinux/utils/unshareselinux.c
>
> diff --git a/libselinux/include/selinux/selinux.h b/libselinux/include/selinux/selinux.h
> index b1431e5d..92186ddc 100644
> --- a/libselinux/include/selinux/selinux.h
> +++ b/libselinux/include/selinux/selinux.h
> @@ -760,6 +760,29 @@ 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);
> +
> +/*
> + * Returns 1 if the SELinux namespace was unshared and has not
> + * yet been fully initialized (i.e. policy not yet loaded).
> + * Returns 0 if SELinux namespaces are not supported by the kernel,
> + * or the SELinux namespace was not unshared, or the namespace has
> + * been fully initialized already.
> + * Return < 0 on any error other than ENOSYS or EOPNOTSUPP.
> + */
> +extern int is_selinux_unshared(void);
> +
>  #ifdef __cplusplus
>  }
>  #endif
> diff --git a/libselinux/src/libselinux.map b/libselinux/src/libselinux.map
> index ab002f01..77f5790c 100644
> --- a/libselinux/src/libselinux.map
> +++ b/libselinux/src/libselinux.map
> @@ -262,3 +262,9 @@ LIBSELINUX_3.9 {
>    global:
>      context_to_str;
>  } LIBSELINUX_3.8;
> +
> +LIBSELINUX_4.0 {
> +  global:
> +    selinux_unshare;
> +    is_selinux_unshared;
> +} LIBSELINUX_3.9;
> diff --git a/libselinux/src/unshare.c b/libselinux/src/unshare.c
> new file mode 100644
> index 00000000..cd3ec78c
> --- /dev/null
> +++ b/libselinux/src/unshare.c
> @@ -0,0 +1,117 @@
> +#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_get_self_attr
> +#define __NR_lsm_get_self_attr 459
> +#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
> +
> +#ifndef HAVE_LSM_GET_SELF_ATTR
> +#define HAVE_LSM_GET_SELF_ATTR 1
> +static int lsm_get_self_attr(unsigned int attr, struct lsm_ctx *ctx,
> +                            uint32_t *size, uint32_t flags)
> +{
> +       return syscall(__NR_lsm_get_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;
> +}
> +
> +struct selinux_ctx {
> +       struct lsm_ctx lsmctx;
> +       char unshared;
> +};
> +
> +/*
> + * Returns 1 if the SELinux namespace was unshared and has not
> + * yet been fully initialized (i.e. policy not yet loaded).
> + * Returns 0 if SELinux namespaces are not supported by the kernel,
> + * or the SELinux namespace was not unshared, or the namespace has
> + * been fully initialized already.
> + * Return < 0 on any error other than ENOSYS or EOPNOTSUPP.
> + */
> +int is_selinux_unshared(void)
> +{
> +       struct selinux_ctx ctx;
> +       uint32_t size = sizeof(ctx);
> +       int ret;
> +
> +       ctx.lsmctx.id = LSM_ID_SELINUX;
> +       ctx.lsmctx.flags = 0;
> +       ctx.lsmctx.len = sizeof(ctx);
> +       ctx.lsmctx.ctx_len = 0;
> +
> +       ret = lsm_get_self_attr(LSM_ATTR_UNSHARE, (struct lsm_ctx *)&ctx,
> +                               &size, LSM_FLAG_SINGLE);
> +       if (ret < 0 && (errno == ENOSYS || errno == EOPNOTSUPP))
> +               return 0;
> +       if (ret < 0)
> +               return ret;
> +       return ctx.unshared;
> +}
> diff --git a/libselinux/utils/.gitignore b/libselinux/utils/.gitignore
> index 2e10b14f..ffd2bc8e 100644
> --- a/libselinux/utils/.gitignore
> +++ b/libselinux/utils/.gitignore
> @@ -30,3 +30,5 @@ setfilecon
>  togglesebool
>  selinux_check_access
>  validatetrans
> +unshareselinux
> +selinuxunshared
> diff --git a/libselinux/utils/selinuxunshared.c b/libselinux/utils/selinuxunshared.c
> new file mode 100644
> index 00000000..965f7ba3
> --- /dev/null
> +++ b/libselinux/utils/selinuxunshared.c
> @@ -0,0 +1,24 @@
> +#include <unistd.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <selinux/selinux.h>
> +
> +int main(int argc, char **argv)
> +{
> +       int ret;
> +
> +       if (argc != 1) {
> +               fprintf(stderr, "usage: %s\n", argv[0]);
> +               exit(-1);
> +       }
> +
> +       ret = is_selinux_unshared();
> +       if (ret < 0) {
> +               perror(argv[0]);
> +               exit(-1);
> +       }
> +
> +       printf("%d\n", ret);
> +
> +       exit(!ret);
> +}
> 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.51.0
>

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [RFC PATCH v3] libselinux: add selinux_unshare() and is_selinux_unshared()
  2025-10-09 13:24 [RFC PATCH v3] libselinux: add selinux_unshare() and is_selinux_unshared() Stephen Smalley
  2025-10-09 13:52 ` Stephen Smalley
@ 2025-10-10 15:40 ` Christian Göttsche
  1 sibling, 0 replies; 3+ messages in thread
From: Christian Göttsche @ 2025-10-10 15:40 UTC (permalink / raw)
  To: Stephen Smalley; +Cc: selinux, paul, john.johansen, casey, serge

Oct 9, 2025 15:28:19 Stephen Smalley <stephen.smalley.work@gmail.com>:

> RFC only, this demonstrates how to implement unsharing of the SELinux
> namespace using the lsm_set_self_attr(LSM_ATTR_UNSHARE, ...) system
> call and how to implement detection of whether one is in an unshared
> SELinux namespace that has not yet been fully initialized using
> the lsm_get_self_attr(LSM_ATTR_UNSHARE, ...) system call.
>
> Provide a selinux_unshare() wrapper for
> lsm_set_self_attr(LSM_ATTR_UNSHARE, ...) and other required processing
> when unsharing the SELinux namespace, and an is_selinux_unshared()
> wrapper for lsm_get_self_attr(LSM_ATTR_UNSHARE, ...) for detecting
> whether one is in an unshared SELinux namespace that has not yet
> been fully initialized.
>
> 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.
>
> Add an is_selinux_unshared() interface to detect whether one is in
> an unshared SELinux namespace that has not yet been fully initialized
> (i.e. no policy loaded yet), and a selinuxunshared utility to use
> it from a shell or script.
>
> Signed-off-by: Stephen Smalley <stephen.smalley.work@gmail.com>
> ---
> v3 changes is_selinux_unshared() to return 0 if lsm_get_self_attr(2)
> sets errno to ENOSYS or EOPNOTSUPP to allow callers to treat its
> return value as a boolean and avoid needing to handle these cases
> themselves.
>
> libselinux/include/selinux/selinux.h |  23 ++++++
> libselinux/src/libselinux.map        |   6 ++
> libselinux/src/unshare.c             | 117 +++++++++++++++++++++++++++
> libselinux/utils/.gitignore          |   2 +
> libselinux/utils/selinuxunshared.c   |  24 ++++++
> libselinux/utils/unshareselinux.c    |  42 ++++++++++
> 6 files changed, 214 insertions(+)
> create mode 100644 libselinux/src/unshare.c
> create mode 100644 libselinux/utils/selinuxunshared.c
> create mode 100644 libselinux/utils/unshareselinux.c
>
> diff --git a/libselinux/include/selinux/selinux.h b/libselinux/include/selinux/selinux.h
> index b1431e5d..92186ddc 100644
> --- a/libselinux/include/selinux/selinux.h
> +++ b/libselinux/include/selinux/selinux.h
> @@ -760,6 +760,29 @@ 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);
> +
> +/*
> + * Returns 1 if the SELinux namespace was unshared and has not
> + * yet been fully initialized (i.e. policy not yet loaded).
> + * Returns 0 if SELinux namespaces are not supported by the kernel,
> + * or the SELinux namespace was not unshared, or the namespace has
> + * been fully initialized already.
> + * Return < 0 on any error other than ENOSYS or EOPNOTSUPP.
> + */
> +extern int is_selinux_unshared(void);
> +
> #ifdef __cplusplus
> }
> #endif
> diff --git a/libselinux/src/libselinux.map b/libselinux/src/libselinux.map
> index ab002f01..77f5790c 100644
> --- a/libselinux/src/libselinux.map
> +++ b/libselinux/src/libselinux.map
> @@ -262,3 +262,9 @@ LIBSELINUX_3.9 {
>    global:
>      context_to_str;
> } LIBSELINUX_3.8;
> +
> +LIBSELINUX_4.0 {
> +  global:
> +    selinux_unshare;
> +    is_selinux_unshared;
> +} LIBSELINUX_3.9;
> diff --git a/libselinux/src/unshare.c b/libselinux/src/unshare.c
> new file mode 100644
> index 00000000..cd3ec78c
> --- /dev/null
> +++ b/libselinux/src/unshare.c
> @@ -0,0 +1,117 @@
> +#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_get_self_attr
> +#define __NR_lsm_get_self_attr 459
> +#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
> +
> +#ifndef HAVE_LSM_GET_SELF_ATTR
> +#define HAVE_LSM_GET_SELF_ATTR 1
> +static int lsm_get_self_attr(unsigned int attr, struct lsm_ctx *ctx,
> +                uint32_t *size, uint32_t flags)
> +{
> +   return syscall(__NR_lsm_get_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;
> +}
> +
> +struct selinux_ctx {
> +   struct lsm_ctx lsmctx;
> +   char unshared;
> +};
> +
> +/*
> + * Returns 1 if the SELinux namespace was unshared and has not
> + * yet been fully initialized (i.e. policy not yet loaded).
> + * Returns 0 if SELinux namespaces are not supported by the kernel,
> + * or the SELinux namespace was not unshared, or the namespace has
> + * been fully initialized already.
> + * Return < 0 on any error other than ENOSYS or EOPNOTSUPP.
> + */
> +int is_selinux_unshared(void)
> +{
> +   struct selinux_ctx ctx;
> +   uint32_t size = sizeof(ctx);
> +   int ret;
> +
> +   ctx.lsmctx.id = LSM_ID_SELINUX;
> +   ctx.lsmctx.flags = 0;
> +   ctx.lsmctx.len = sizeof(ctx);
> +   ctx.lsmctx.ctx_len = 0;
> +
> +   ret = lsm_get_self_attr(LSM_ATTR_UNSHARE, (struct lsm_ctx *)&ctx,
> +               &size, LSM_FLAG_SINGLE);
> +   if (ret < 0 && (errno == ENOSYS || errno == EOPNOTSUPP))
> +       return 0;
> +   if (ret < 0)
> +       return ret;
> +   return ctx.unshared;
> +}
> diff --git a/libselinux/utils/.gitignore b/libselinux/utils/.gitignore
> index 2e10b14f..ffd2bc8e 100644
> --- a/libselinux/utils/.gitignore
> +++ b/libselinux/utils/.gitignore
> @@ -30,3 +30,5 @@ setfilecon
> togglesebool
> selinux_check_access
> validatetrans
> +unshareselinux
> +selinuxunshared
> diff --git a/libselinux/utils/selinuxunshared.c b/libselinux/utils/selinuxunshared.c
> new file mode 100644
> index 00000000..965f7ba3
> --- /dev/null
> +++ b/libselinux/utils/selinuxunshared.c
> @@ -0,0 +1,24 @@
> +#include <unistd.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <selinux/selinux.h>
> +
> +int main(int argc, char **argv)
> +{
> +   int ret;
> +
> +   if (argc != 1) {
> +       fprintf(stderr, "usage: %s\n", argv[0]);
> +       exit(-1);
> +   }
> +
> +   ret = is_selinux_unshared();
> +   if (ret < 0) {
> +       perror(argv[0]);
> +       exit(-1);
> +   }
> +
> +   printf("%d\n", ret);
> +
> +   exit(!ret);
> +}
> 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]);

Nit picking:

+ exit(1);

> +}
> --
> 2.51.0


^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2025-10-10 15:40 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-10-09 13:24 [RFC PATCH v3] libselinux: add selinux_unshare() and is_selinux_unshared() Stephen Smalley
2025-10-09 13:52 ` Stephen Smalley
2025-10-10 15:40 ` Christian Göttsche

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).