All of lore.kernel.org
 help / color / mirror / Atom feed
* [pseudo][PATCH 0/6] Add landlock support, fix seccomp
@ 2026-11-12 16:19 Mark Hatle
  2026-11-12 16:19 ` [pseudo][PATCH 1/6] test-landlock: Add a Landlock sandbox test Mark Hatle
                   ` (5 more replies)
  0 siblings, 6 replies; 7+ messages in thread
From: Mark Hatle @ 2026-11-12 16:19 UTC (permalink / raw)
  To: yocto-patches

The first 3 changes add support for landlock.  This was recently
reported by Richard Tollerton that xz on arch linux was now using
landlock and breaking pseudo invocations.  The first patch is a
test case, second patch moves to using /proc instead of a chdir
(with fallback to prior behavior) and third patch intercepts the
syscall and avoids landlock.  This was implemented as a belt and
suspenders type approach, as there have been instances of similar
software in the past using non-standard ways to activate.

During review and testing, it was observed that the prctl/seccomp
implementation was incomplete.  Add tests, and then fix the
existing implementation.

Mark Hatle (6):
  test-landlock: Add a Landlock sandbox test
  pseudo_client: Connect to the server without changing directory
  ports/linux/pseudo_wrappers: Report Landlock as unsupported
  test-seccomp: Check that seccomp is never really enabled
  ports/linux/pseudo_wrappers: Fake seccomp filters set by prctl()
  ports/linux/pseudo_wrappers: Fake seccomp strict mode in syscall()

 README.security               |  14 ++++
 ports/linux/pseudo_wrappers.c |  29 +++++++--
 pseudo_client.c               |  77 +++++++++++++++-------
 test/test-landlock.c          | 146 ++++++++++++++++++++++++++++++++++++++++++
 test/test-landlock.sh         |  24 +++++++
 test/test-seccomp.c           | 121 ++++++++++++++++++++++++++++++++++
 test/test-seccomp.sh          |  12 ++++
 7 files changed, 395 insertions(+), 28 deletions(-)
 create mode 100644 test/test-landlock.c
 create mode 100755 test/test-landlock.sh
 create mode 100644 test/test-seccomp.c
 create mode 100755 test/test-seccomp.sh

-- 
1.8.3.1


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

* [pseudo][PATCH 1/6] test-landlock: Add a Landlock sandbox test
  2026-11-12 16:19 [pseudo][PATCH 0/6] Add landlock support, fix seccomp Mark Hatle
@ 2026-11-12 16:19 ` Mark Hatle
  2026-11-12 16:19 ` [pseudo][PATCH 2/6] pseudo_client: Connect to the server without changing directory Mark Hatle
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Mark Hatle @ 2026-11-12 16:19 UTC (permalink / raw)
  To: yocto-patches

From: Mark Hatle <mark.hatle@amd.com>

[YOCTO #16493]

AI-Generated: Uses GitHub Copilot

Reported-by: Richard Tollerton <rich.tollerton@ni.com>
Signed-off-by: Mark Hatle <mark.hatle@amd.com>
---
 test/test-landlock.c  | 146 ++++++++++++++++++++++++++++++++++++++++++++++++++
 test/test-landlock.sh |  24 +++++++++
 2 files changed, 170 insertions(+)
 create mode 100644 test/test-landlock.c
 create mode 100755 test/test-landlock.sh

diff --git a/test/test-landlock.c b/test/test-landlock.c
new file mode 100644
index 0000000..e9bce88
--- /dev/null
+++ b/test/test-landlock.c
@@ -0,0 +1,146 @@
+/*
+ * SPDX-License-Identifier: LGPL-2.1-only
+ *
+ * xz (5.6 and later) sandboxes itself with Landlock when it works on a
+ * pipe, revoking every filesystem access right before it does any work.
+ * libpseudo only connects to the server on the first operation it has to
+ * report, and connecting needs filesystem access (open(".") and fchdir()
+ * to the state directory, the pidfile, spawning a server), so under pseudo
+ * the first operation after the sandbox goes up fails. Do what xz does,
+ * then fstat() a descriptor that was opened beforehand and check that
+ * pseudo still reports the ownership it recorded for it.
+ *
+ * If libpseudo refuses Landlock (as it does seccomp), carry on without the
+ * sandbox the way xz does; only the result of the fstat() matters.
+ */
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/prctl.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#include <linux/types.h>
+
+/* matches test-openat2-syscall: the shell wrapper turns 77 into "skipped" */
+#define SKIP 77
+
+/* <linux/landlock.h> and the syscall numbers are missing on hosts with
+ * pre-5.13 kernel headers.  The numbers are the same on every architecture.
+ */
+#ifndef __NR_landlock_create_ruleset
+#define __NR_landlock_create_ruleset 444
+#endif
+#ifndef __NR_landlock_restrict_self
+#define __NR_landlock_restrict_self 446
+#endif
+
+struct test_landlock_ruleset_attr {
+	__u64 handled_access_fs;
+};
+
+#define TEST_LANDLOCK_CREATE_RULESET_VERSION	(1U << 0)
+
+/* ABI 1: 13 rights, then REFER (2), TRUNCATE (3), IOCTL_DEV (5) */
+#define TEST_LANDLOCK_ACCESS_FS_ABI1		((1ULL << 13) - 1)
+#define TEST_LANDLOCK_ACCESS_FS_REFER		(1ULL << 13)
+#define TEST_LANDLOCK_ACCESS_FS_TRUNCATE	(1ULL << 14)
+#define TEST_LANDLOCK_ACCESS_FS_IOCTL_DEV	(1ULL << 15)
+
+static int landlock_abi(void) {
+	return (int) syscall(__NR_landlock_create_ruleset, NULL, 0,
+		TEST_LANDLOCK_CREATE_RULESET_VERSION);
+}
+
+/* Is Landlock there at all? The wrapper runs this with PSEUDO_DISABLED
+ * set, so the answer is the kernel's.
+ */
+static int probe(void) {
+	if (landlock_abi() < 1) {
+		if (errno == ENOSYS || errno == EOPNOTSUPP)
+			return SKIP;
+		perror("landlock_create_ruleset(VERSION)");
+		return 1;
+	}
+	return 0;
+}
+
+/* Revoke every filesystem access right the kernel knows about, as xz's
+ * sandbox_enable_strict_if_allowed() does. Returns 0 if the sandbox is up,
+ * 1 if Landlock is not available, -1 on error.
+ */
+static int sandbox(void) {
+	struct test_landlock_ruleset_attr attr = { 0 };
+	int abi, fd;
+
+	abi = landlock_abi();
+	if (abi < 1) {
+		if (errno == ENOSYS || errno == EOPNOTSUPP)
+			return 1;
+		perror("landlock_create_ruleset(VERSION)");
+		return -1;
+	}
+	attr.handled_access_fs = TEST_LANDLOCK_ACCESS_FS_ABI1;
+	if (abi >= 2)
+		attr.handled_access_fs |= TEST_LANDLOCK_ACCESS_FS_REFER;
+	if (abi >= 3)
+		attr.handled_access_fs |= TEST_LANDLOCK_ACCESS_FS_TRUNCATE;
+	if (abi >= 5)
+		attr.handled_access_fs |= TEST_LANDLOCK_ACCESS_FS_IOCTL_DEV;
+
+	fd = (int) syscall(__NR_landlock_create_ruleset, &attr, sizeof(attr), 0);
+	if (fd == -1) {
+		perror("landlock_create_ruleset");
+		return -1;
+	}
+	if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
+		perror("prctl(PR_SET_NO_NEW_PRIVS)");
+		close(fd);
+		return -1;
+	}
+	if (syscall(__NR_landlock_restrict_self, fd, 0) == -1) {
+		perror("landlock_restrict_self");
+		close(fd);
+		return -1;
+	}
+	close(fd);
+	return 0;
+}
+
+int main(int argc, char **argv) {
+	struct stat st;
+	uid_t uid;
+	gid_t gid;
+	int rc;
+
+	if (argc > 1 && !strcmp(argv[1], "--probe"))
+		return probe();
+	if (argc != 3) {
+		fprintf(stderr, "usage: %s --probe | uid gid < file\n", argv[0]);
+		return 1;
+	}
+	uid = (uid_t) strtoul(argv[1], NULL, 10);
+	gid = (gid_t) strtoul(argv[2], NULL, 10);
+
+	/* nothing above this point has needed the pseudo server */
+	rc = sandbox();
+	if (rc < 0)
+		return 1;
+	if (rc > 0)
+		fprintf(stderr, "Landlock unavailable, running unsandboxed\n");
+
+	if (fstat(0, &st) == -1) {
+		perror("fstat(stdin)");
+		return 1;
+	}
+	if (st.st_uid != uid || st.st_gid != gid) {
+		fprintf(stderr, "fstat(stdin): owner %d:%d, expected %d:%d\n",
+			(int) st.st_uid, (int) st.st_gid, (int) uid, (int) gid);
+		return 1;
+	}
+	return 0;
+}
diff --git a/test/test-landlock.sh b/test/test-landlock.sh
new file mode 100755
index 0000000..1514236
--- /dev/null
+++ b/test/test-landlock.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+#
+# SPDX-License-Identifier: LGPL-2.1-only
+#
+
+# Landlock needs a 5.13 kernel built with it enabled. Ask with pseudo out of
+# the way, so that pseudo refusing Landlock is not mistaken for the kernel
+# lacking it.
+PSEUDO_DISABLED=1 ./test/test-landlock --probe
+case $? in
+0)  ;;
+77) exit 255 ;;
+*)  exit 1 ;;
+esac
+
+file=test-landlock-file
+trap "rm -f $file" 0
+
+echo foo > $file || exit 1
+chown 1234:5678 $file || exit 1
+
+# A client that cannot reach the server retries for about 90 seconds with
+# SIGTERM blocked by the wrapper, so stop it with SIGKILL instead.
+timeout -s KILL 10s ./test/test-landlock 1234 5678 < $file
-- 
1.8.3.1


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

* [pseudo][PATCH 2/6] pseudo_client: Connect to the server without changing directory
  2026-11-12 16:19 [pseudo][PATCH 0/6] Add landlock support, fix seccomp Mark Hatle
  2026-11-12 16:19 ` [pseudo][PATCH 1/6] test-landlock: Add a Landlock sandbox test Mark Hatle
@ 2026-11-12 16:19 ` Mark Hatle
  2026-11-12 16:19 ` [pseudo][PATCH 3/6] ports/linux/pseudo_wrappers: Report Landlock as unsupported Mark Hatle
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Mark Hatle @ 2026-11-12 16:19 UTC (permalink / raw)
  To: yocto-patches

From: Mark Hatle <mark.hatle@amd.com>

The socket paths can be too long for sun_path, so client_connect()
saved the working directory with open("."), changed to the state
directory, and changed back.  A Landlock sandbox that revokes
filesystem access, as xz sets up when working on a pipe, denies that
open(), so a process that sandboxes itself before its first pseudo
operation can never reach the server.  Name the directory as
/proc/self/fd/<fd>/ instead, which also stops connecting from moving
other threads' working directory.  Fall back to changing directory if
/proc is not mounted or the path doesn't fit in sun_path.

This makes test-landlock pass.  Both fallbacks were checked by hiding
/proc in a mount namespace and by forcing the length check to fail.

[YOCTO #16493]

AI-Generated: Uses GitHub Copilot
Signed-off-by: Mark Hatle <mark.hatle@amd.com>
---
 pseudo_client.c | 77 +++++++++++++++++++++++++++++++++++++++------------------
 1 file changed, 53 insertions(+), 24 deletions(-)

diff --git a/pseudo_client.c b/pseudo_client.c
index 58e8810..fe111cf 100644
--- a/pseudo_client.c
+++ b/pseudo_client.c
@@ -1253,8 +1253,31 @@ pseudo_client_path_matches_fd(const char *path, int fd, int nofollow) {
 static int
 client_connect(void) {
 	/* we have a server pid, is it responsive? */
-	struct sockaddr_un sun = { .sun_family = AF_UNIX, .sun_path = PSEUDO_SOCKET };
-	int cwd_fd;
+	struct sockaddr_un sun = { .sun_family = AF_UNIX };
+	struct stat st;
+	int cwd_fd = -1;
+	int use_chdir;
+	int len;
+
+	/* The full socket path can be too long for sun_path, so name the
+	 * state directory by its descriptor under /proc/self/fd.  Unlike
+	 * changing directory, that needs no open("."), which a Landlock
+	 * sandbox can deny, and doesn't move other threads' working
+	 * directory.  Without /proc, or if that path doesn't fit, change
+	 * directory as before.
+	 */
+	len = snprintf(sun.sun_path, sizeof(sun.sun_path), "/proc/self/fd/%d",
+		pseudo_localstate_dir_fd);
+	if (len > 0 && (size_t) len + 1 + strlen(PSEUDO_SOCKET) < sizeof(sun.sun_path) &&
+	    stat(sun.sun_path, &st) == 0 && S_ISDIR(st.st_mode)) {
+		snprintf(sun.sun_path + len, sizeof(sun.sun_path) - len, "/%s",
+			PSEUDO_SOCKET);
+		use_chdir = 0;
+	} else {
+		pseudo_debug(PDBGF_CLIENT, "/proc/self/fd unusable or path too long, changing directory to connect.\n");
+		snprintf(sun.sun_path, sizeof(sun.sun_path), "%s", PSEUDO_SOCKET);
+		use_chdir = 1;
+	}
 
 	connect_fd = socket(PF_UNIX, SOCK_STREAM, 0);
 	connect_fd = pseudo_fd(connect_fd, MOVE_FD);
@@ -1267,40 +1290,46 @@ client_connect(void) {
 	}
 
 	pseudo_debug(PDBGF_CLIENT, "connecting socket...\n");
-	cwd_fd = open(".", O_RDONLY);
-	if (cwd_fd == -1) {
-		pseudo_error("Couldn't stash directory before opening socket: %s",
-			strerror(errno));
-		close(connect_fd);
-		connect_fd = -1;
-		return 1;
-	}
-	if (fchdir(pseudo_localstate_dir_fd) == -1) {
-		pseudo_error("Couldn't chdir to server directory [%d]: %s\n",
-			pseudo_localstate_dir_fd, strerror(errno));
-		close(connect_fd);
-		close(cwd_fd);
-		connect_fd = -1;
-		return 1;
+	if (use_chdir) {
+		cwd_fd = open(".", O_RDONLY);
+		if (cwd_fd == -1) {
+			pseudo_error("Couldn't stash directory before opening socket: %s\n",
+				strerror(errno));
+			close(connect_fd);
+			connect_fd = -1;
+			return 1;
+		}
+		if (fchdir(pseudo_localstate_dir_fd) == -1) {
+			pseudo_error("Couldn't chdir to server directory [%d]: %s\n",
+				pseudo_localstate_dir_fd, strerror(errno));
+			close(connect_fd);
+			close(cwd_fd);
+			connect_fd = -1;
+			return 1;
+		}
 	}
 	if (connect(connect_fd, (struct sockaddr *) &sun, sizeof(sun)) == -1) {
 		char *e = strerror(errno);
 		pseudo_debug(PDBGF_CLIENT, "Can't connect socket to pseudo.socket: (%s)\n", e);
 		pseudo_evlog(PDBGF_CLIENT, "connect failed: %s\n", e);
 		close(connect_fd);
+		if (use_chdir) {
+			if (fchdir(cwd_fd) == -1) {
+				pseudo_error("return to previous directory failed: %s\n",
+					strerror(errno));
+			}
+			close(cwd_fd);
+		}
+		connect_fd = -1;
+		return 1;
+	}
+	if (use_chdir) {
 		if (fchdir(cwd_fd) == -1) {
 			pseudo_error("return to previous directory failed: %s\n",
 				strerror(errno));
 		}
 		close(cwd_fd);
-		connect_fd = -1;
-		return 1;
-	}
-	if (fchdir(cwd_fd) == -1) {
-		pseudo_error("return to previous directory failed: %s\n",
-			strerror(errno));
 	}
-	close(cwd_fd);
 	pseudo_evlog(PDBGF_CLIENT, "socket connect OK.\n");
 	pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "connected socket.\n");
 	return 0;
-- 
1.8.3.1


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

* [pseudo][PATCH 3/6] ports/linux/pseudo_wrappers: Report Landlock as unsupported
  2026-11-12 16:19 [pseudo][PATCH 0/6] Add landlock support, fix seccomp Mark Hatle
  2026-11-12 16:19 ` [pseudo][PATCH 1/6] test-landlock: Add a Landlock sandbox test Mark Hatle
  2026-11-12 16:19 ` [pseudo][PATCH 2/6] pseudo_client: Connect to the server without changing directory Mark Hatle
@ 2026-11-12 16:19 ` Mark Hatle
  2026-11-12 16:19 ` [pseudo][PATCH 4/6] test-seccomp: Check that seccomp is never really enabled Mark Hatle
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Mark Hatle @ 2026-11-12 16:19 UTC (permalink / raw)
  To: yocto-patches

From: Mark Hatle <mark.hatle@amd.com>

A Landlock sandbox restricts libpseudo along with the program that sets
it up, so pseudo can lose the server for good: reconnecting may need to
start a server, read the pidfile or fall back to changing directory,
and later operations need files such as passwd and group.  Fail the
Landlock system calls with ENOSYS, as an unsupporting kernel would,
much as seccomp filters are already ignored.  Leave them alone while
pseudo is disabled, so tests can still ask the kernel about Landlock.
Document both in README.security.

Tested with test-landlock, also with the client forced onto the
fchdir() connect fallback, which this alone is enough to fix.

[YOCTO #16493]

AI-Generated: Uses GitHub Copilot
Signed-off-by: Mark Hatle <mark.hatle@amd.com>
---
 README.security               | 14 ++++++++++++++
 ports/linux/pseudo_wrappers.c | 18 ++++++++++++++++++
 2 files changed, 32 insertions(+)

diff --git a/README.security b/README.security
index 45533f2..11b9b4b 100644
--- a/README.security
+++ b/README.security
@@ -141,6 +141,20 @@ that user.  The resulting metadata can only be trusted as much as every
 process running as the build user.
 
 
+7. SELF-SANDBOXING IS DISABLED
+
+libpseudo has to keep reaching the server and the filesystem on behalf
+of the program it is loaded into, so pseudo turns off the ways a
+program can restrict itself (ports/linux/pseudo_wrappers.c).  Requests
+to install a seccomp filter through syscall() or prctl() report success
+without installing anything, and the Landlock system calls fail with
+ENOSYS, as if the kernel did not support Landlock.  A program that
+sandboxes itself, such as xz, runs unsandboxed under pseudo, and one
+that refuses to run without its sandbox fails.  This doesn't apply to
+programs pseudo does not intercept (see section 1), and the Landlock
+calls reach the kernel while PSEUDO_DISABLED is set.
+
+
 RECOMMENDATIONS:
 
 * Run pseudo only on build output that is private to the build and is
diff --git a/ports/linux/pseudo_wrappers.c b/ports/linux/pseudo_wrappers.c
index 5e26aff..7d1e7d9 100644
--- a/ports/linux/pseudo_wrappers.c
+++ b/ports/linux/pseudo_wrappers.c
@@ -77,6 +77,11 @@ syscall(long number, ...) {
 #ifdef SYS_seccomp
 	    && number != SYS_seccomp
 #endif
+#ifdef SYS_landlock_create_ruleset
+	    && number != SYS_landlock_create_ruleset
+	    && number != SYS_landlock_add_rule
+	    && number != SYS_landlock_restrict_self
+#endif
 	    ) {
 		if (!real_syscall)
 			real_syscall = (long (*)(long, ...)) dlsym(RTLD_NEXT, "syscall");
@@ -109,6 +114,19 @@ syscall(long number, ...) {
                 goto call_syscall;
         }
 
+#ifdef SYS_landlock_create_ruleset
+	/* A Landlock sandbox also binds libpseudo, which then can't reach the
+	 * server (YOCTO #16493), so report Landlock as unsupported.
+	 */
+	if (number == SYS_landlock_create_ruleset ||
+	    number == SYS_landlock_add_rule ||
+	    number == SYS_landlock_restrict_self) {
+		pseudo_debug(PDBGF_SYSCALL, "syscall, refusing landlock.\n");
+		errno = ENOSYS;
+		return -1;
+	}
+#endif
+
 #ifdef SYS_openat2
 	/* There is a CVE patch (CVE-2025-45582) to tar 1.34 in Centos Stream which
 	 * uses syscall to access openat2() and breaks builds if we don't redirect.
-- 
1.8.3.1


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

* [pseudo][PATCH 4/6] test-seccomp: Check that seccomp is never really enabled
  2026-11-12 16:19 [pseudo][PATCH 0/6] Add landlock support, fix seccomp Mark Hatle
                   ` (2 preceding siblings ...)
  2026-11-12 16:19 ` [pseudo][PATCH 3/6] ports/linux/pseudo_wrappers: Report Landlock as unsupported Mark Hatle
@ 2026-11-12 16:19 ` Mark Hatle
  2026-11-12 16:19 ` [pseudo][PATCH 5/6] ports/linux/pseudo_wrappers: Fake seccomp filters set by prctl() Mark Hatle
  2026-11-12 16:19 ` [pseudo][PATCH 6/6] ports/linux/pseudo_wrappers: Fake seccomp strict mode in syscall() Mark Hatle
  5 siblings, 0 replies; 7+ messages in thread
From: Mark Hatle @ 2026-11-12 16:19 UTC (permalink / raw)
  To: yocto-patches

From: Mark Hatle <mark.hatle@amd.com>

Ask for seccomp strict and filter mode, by prctl() and by the seccomp()
system call, each in a child process, and check the call succeeds and
that /proc/self/status still reports Seccomp: 0.

AI-Generated: Uses GitHub Copilot
Signed-off-by: Mark Hatle <mark.hatle@amd.com>
---
 test/test-seccomp.c  | 121 +++++++++++++++++++++++++++++++++++++++++++++++++++
 test/test-seccomp.sh |  12 +++++
 2 files changed, 133 insertions(+)
 create mode 100644 test/test-seccomp.c
 create mode 100755 test/test-seccomp.sh

diff --git a/test/test-seccomp.c b/test/test-seccomp.c
new file mode 100644
index 0000000..b600716
--- /dev/null
+++ b/test/test-seccomp.c
@@ -0,0 +1,121 @@
+/*
+ * SPDX-License-Identifier: LGPL-2.1-only
+ *
+ * A seccomp sandbox restricts libpseudo along with the program, so pseudo
+ * pretends to install one.  Check that every way of asking for one, by
+ * prctl() and by the seccomp() system call, in strict and filter mode,
+ * returns success without leaving the process under seccomp.  Strict mode
+ * would kill the process at the next call other than read(), write() or
+ * exit(), so each case runs in a child of its own.
+ */
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/prctl.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <linux/filter.h>
+#include <linux/seccomp.h>
+
+/* matches test-openat2-syscall: the shell wrapper turns 77 into "skipped" */
+#define SKIP 77
+
+/* Seccomp: field of /proc/self/status, or -1 if there is none */
+static int seccomp_mode(void) {
+	char line[256];
+	int mode = -1;
+	FILE *f;
+
+	f = fopen("/proc/self/status", "r");
+	if (!f)
+		return -1;
+	while (fgets(line, sizeof(line), f))
+		if (!strncmp(line, "Seccomp:", 8))
+			mode = atoi(line + 8);
+	fclose(f);
+	return mode;
+}
+
+static struct sock_filter allow_all[] = {
+	BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
+};
+static struct sock_fprog prog = {
+	.len = sizeof(allow_all) / sizeof(allow_all[0]),
+	.filter = allow_all,
+};
+
+static long request(const char *how) {
+	if (!strcmp(how, "prctl-strict"))
+		return prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT, 0, 0, 0);
+	if (!strcmp(how, "prctl-filter"))
+		return prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog, 0, 0);
+	if (!strcmp(how, "syscall-strict"))
+		return syscall(SYS_seccomp, SECCOMP_SET_MODE_STRICT, 0, NULL);
+	if (!strcmp(how, "syscall-filter"))
+		return syscall(SYS_seccomp, SECCOMP_SET_MODE_FILTER, 0, &prog);
+	errno = EINVAL;
+	return -1;
+}
+
+static int check(const char *how) {
+	int status;
+	pid_t pid;
+
+	fflush(NULL);
+	pid = fork();
+	if (pid == -1) {
+		perror("fork");
+		return 1;
+	}
+	if (pid == 0) {
+		long rc;
+		int mode;
+
+		if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
+			perror("prctl(PR_SET_NO_NEW_PRIVS)");
+			_exit(1);
+		}
+		rc = request(how);
+		if (rc != 0) {
+			fprintf(stderr, "%s: returned %ld (%s)\n", how, rc,
+				rc == -1 ? strerror(errno) : "?");
+			_exit(1);
+		}
+		mode = seccomp_mode();
+		if (mode != 0) {
+			fprintf(stderr, "%s: Seccomp: %d after the call\n", how, mode);
+			_exit(1);
+		}
+		_exit(0);
+	}
+	if (waitpid(pid, &status, 0) == -1) {
+		perror("waitpid");
+		return 1;
+	}
+	if (WIFSIGNALED(status)) {
+		fprintf(stderr, "%s: killed by signal %d\n", how, WTERMSIG(status));
+		return 1;
+	}
+	return WEXITSTATUS(status) != 0;
+}
+
+int main(void) {
+	static const char *hows[] = {
+		"prctl-strict", "prctl-filter", "syscall-strict", "syscall-filter",
+	};
+	size_t i;
+	int failed = 0;
+
+	if (seccomp_mode() == -1) {
+		fprintf(stderr, "no Seccomp: in /proc/self/status\n");
+		return SKIP;
+	}
+	for (i = 0; i < sizeof(hows) / sizeof(hows[0]); ++i)
+		failed |= check(hows[i]);
+	return failed;
+}
diff --git a/test/test-seccomp.sh b/test/test-seccomp.sh
new file mode 100755
index 0000000..396e314
--- /dev/null
+++ b/test/test-seccomp.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+#
+# SPDX-License-Identifier: LGPL-2.1-only
+#
+
+# rc 77 means the kernel doesn't report seccomp status, so we're skipped
+./test/test-seccomp
+case $? in
+0)  exit 0 ;;
+77) exit 255 ;;
+*)  exit 1 ;;
+esac
-- 
1.8.3.1


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

* [pseudo][PATCH 5/6] ports/linux/pseudo_wrappers: Fake seccomp filters set by prctl()
  2026-11-12 16:19 [pseudo][PATCH 0/6] Add landlock support, fix seccomp Mark Hatle
                   ` (3 preceding siblings ...)
  2026-11-12 16:19 ` [pseudo][PATCH 4/6] test-seccomp: Check that seccomp is never really enabled Mark Hatle
@ 2026-11-12 16:19 ` Mark Hatle
  2026-11-12 16:19 ` [pseudo][PATCH 6/6] ports/linux/pseudo_wrappers: Fake seccomp strict mode in syscall() Mark Hatle
  5 siblings, 0 replies; 7+ messages in thread
From: Mark Hatle @ 2026-11-12 16:19 UTC (permalink / raw)
  To: yocto-patches

From: Mark Hatle <mark.hatle@amd.com>

prctl(PR_SET_SECCOMP) was compared against SECCOMP_SET_MODE_FILTER,
which is the seccomp() system call's value.  prctl() takes
SECCOMP_MODE_*, where that value means strict mode, so prctl() filters
were really installed and strict mode was faked by accident.  Fake both
SECCOMP_MODE_FILTER and SECCOMP_MODE_STRICT.

AI-Generated: Uses GitHub Copilot
Signed-off-by: Mark Hatle <mark.hatle@amd.com>
---
 ports/linux/pseudo_wrappers.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/ports/linux/pseudo_wrappers.c b/ports/linux/pseudo_wrappers.c
index 7d1e7d9..e3cf18b 100644
--- a/ports/linux/pseudo_wrappers.c
+++ b/ports/linux/pseudo_wrappers.c
@@ -240,15 +240,18 @@ prctl(int option, ...) {
 		return rc;
 	}
 
-#ifdef SECCOMP_SET_MODE_FILTER
+#ifdef PR_SET_SECCOMP
 	/* pseudo and seccomp are incompatible as pseudo uses different syscalls
-	 * so pretend to enable seccomp but really do nothing */
+	 * so pretend to enable seccomp but really do nothing.  prctl() takes
+	 * SECCOMP_MODE_*, not the SECCOMP_SET_MODE_* values seccomp() takes.
+	 */
 	if (option == PR_SET_SECCOMP) {
 		unsigned long cmd;
 		va_start(ap, option);
 		cmd = va_arg(ap, unsigned long);
 		va_end(ap);
-		if (cmd == SECCOMP_SET_MODE_FILTER) {
+		if (cmd == SECCOMP_MODE_STRICT || cmd == SECCOMP_MODE_FILTER) {
+		    pseudo_debug(PDBGF_SYSCALL, "prctl, faking seccomp.\n");
 		    return 0;
 		}
 	}
-- 
1.8.3.1


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

* [pseudo][PATCH 6/6] ports/linux/pseudo_wrappers: Fake seccomp strict mode in syscall()
  2026-11-12 16:19 [pseudo][PATCH 0/6] Add landlock support, fix seccomp Mark Hatle
                   ` (4 preceding siblings ...)
  2026-11-12 16:19 ` [pseudo][PATCH 5/6] ports/linux/pseudo_wrappers: Fake seccomp filters set by prctl() Mark Hatle
@ 2026-11-12 16:19 ` Mark Hatle
  5 siblings, 0 replies; 7+ messages in thread
From: Mark Hatle @ 2026-11-12 16:19 UTC (permalink / raw)
  To: yocto-patches

From: Mark Hatle <mark.hatle@amd.com>

seccomp(SECCOMP_SET_MODE_STRICT) reached the kernel, which then kills
the process at its next system call other than read(), write() or
exit(), and libpseudo makes plenty of those.  Fake it like filter mode,
as prctl() now does, and say so in README.security.

AI-Generated: Uses GitHub Copilot
Signed-off-by: Mark Hatle <mark.hatle@amd.com>
---
 README.security               | 12 ++++++------
 ports/linux/pseudo_wrappers.c |  2 +-
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/README.security b/README.security
index 11b9b4b..7249b5d 100644
--- a/README.security
+++ b/README.security
@@ -146,12 +146,12 @@ process running as the build user.
 libpseudo has to keep reaching the server and the filesystem on behalf
 of the program it is loaded into, so pseudo turns off the ways a
 program can restrict itself (ports/linux/pseudo_wrappers.c).  Requests
-to install a seccomp filter through syscall() or prctl() report success
-without installing anything, and the Landlock system calls fail with
-ENOSYS, as if the kernel did not support Landlock.  A program that
-sandboxes itself, such as xz, runs unsandboxed under pseudo, and one
-that refuses to run without its sandbox fails.  This doesn't apply to
-programs pseudo does not intercept (see section 1), and the Landlock
+to enter seccomp strict or filter mode through syscall() or prctl()
+report success without doing anything, and the Landlock system calls
+fail with ENOSYS, as if the kernel did not support Landlock.  A program
+that sandboxes itself, such as xz, runs unsandboxed under pseudo, and
+one that refuses to run without its sandbox fails.  This doesn't apply
+to programs pseudo does not intercept (see section 1), and the Landlock
 calls reach the kernel while PSEUDO_DISABLED is set.
 
 
diff --git a/ports/linux/pseudo_wrappers.c b/ports/linux/pseudo_wrappers.c
index e3cf18b..e8a2780 100644
--- a/ports/linux/pseudo_wrappers.c
+++ b/ports/linux/pseudo_wrappers.c
@@ -104,7 +104,7 @@ syscall(long number, ...) {
 		va_start(ap, number);
 		cmd = va_arg(ap, unsigned long);
 		va_end(ap);
-		if (cmd == SECCOMP_SET_MODE_FILTER) {
+		if (cmd == SECCOMP_SET_MODE_STRICT || cmd == SECCOMP_SET_MODE_FILTER) {
 		    return 0;
 		}
 	}
-- 
1.8.3.1


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

end of thread, other threads:[~2026-11-12 16:20 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-12 16:19 [pseudo][PATCH 0/6] Add landlock support, fix seccomp Mark Hatle
2026-11-12 16:19 ` [pseudo][PATCH 1/6] test-landlock: Add a Landlock sandbox test Mark Hatle
2026-11-12 16:19 ` [pseudo][PATCH 2/6] pseudo_client: Connect to the server without changing directory Mark Hatle
2026-11-12 16:19 ` [pseudo][PATCH 3/6] ports/linux/pseudo_wrappers: Report Landlock as unsupported Mark Hatle
2026-11-12 16:19 ` [pseudo][PATCH 4/6] test-seccomp: Check that seccomp is never really enabled Mark Hatle
2026-11-12 16:19 ` [pseudo][PATCH 5/6] ports/linux/pseudo_wrappers: Fake seccomp filters set by prctl() Mark Hatle
2026-11-12 16:19 ` [pseudo][PATCH 6/6] ports/linux/pseudo_wrappers: Fake seccomp strict mode in syscall() Mark Hatle

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.