* [LTP] [PATCH v5 00/14] fchroot: add fchroot() testing suite
@ 2026-10-20 10:16 Andrea Cervesato
2026-10-20 10:16 ` [LTP] [PATCH v5 01/14] lapi: fallback fchroot() parameters Andrea Cervesato
` (14 more replies)
0 siblings, 15 replies; 24+ messages in thread
From: Andrea Cervesato @ 2026-10-20 10:16 UTC (permalink / raw)
To: Linux Test Project
fchroot() is a new syscall introduced in Linux v7.3 together with
failfs, a kernel internal filesystem where every operation fails with
EOPNOTSUPP. The syscall is the fd-based counterpart of chroot(2): it
moves the process root to the directory referenced by a file descriptor,
or to the failfs root via the FD_FAILFS_ROOT sentinel.
The series adds lapi fallback definitions for the new fd sentinels and
adds 13 tests covering:
- the basic directory fd use case and the argument error paths
(EINVAL, EBADF, ENOTDIR)
- the permission checks, both execute permission and CAP_SYS_CHROOT
- entering failfs as root: EOPNOTSUPP on every absolute lookup,
unreachable working directory, root that can not be referenced or
pinned, path walks anchored at file descriptors, execve blocked,
fork inheritance and the setns() escape
- the unprivileged entry rules: no_new_privs, shared fs_struct and
the already chrooted case
The testing suite creation has been assisted by GLM 5.3.
Signed-off-by: Andrea Cervesato <andrea.cervesato@suse.com>
---
Changes in v5:
- drop syscall table commits already merged in master
- drop [STAGING] prefix and move runtest entries to runtest/syscalls
- remove redundant exit(0) in forked test processes
- fchroot04, fchroot11: use /dev/zero instead of /etc/passwd
- fchroot06: simplify upwards path construction and use SAFE_STAT()
- fchroot08, fchroot09: drop unnecessary symlink setup and open "/" directly
- fchroot09: clarify setns() escape description
- fchroot11: check unprivileged userns support before testing failfs block
- Link to v4: https://lore.kernel.org/20260831-fchroot-v4-0-57cceb68f927@suse.com
Changes in v4:
- restore fchroot08 commit
- fix descriptions
- improve setup() inside tests
- Link to v3: https://lore.kernel.org/20260828-fchroot-v3-0-656a2b515726@suse.com
Changes in v3:
- remove order specific tests inside the descriptions
- fchroot07: add child
- fix snprintf() possible overflows
- fix tests using TST_EXP_* macros
- use stat(".", ..) where it's needed
- Link to v2: https://lore.kernel.org/20260820-fchroot-v2-0-062ed20957a0@suse.com
Changes in v2:
- make description more readable
- invert fork() logic for the main test process
- fix the issues found by the agent
- Link to v1: https://lore.kernel.org/20260819-fchroot-v1-0-2dc2c3c3cf29@suse.com
To: Linux Test Project <ltp@lists.linux.it>
---
Andrea Cervesato (14):
lapi: fallback fchroot() parameters
fchroot01: test fchroot() with a directory fd
fchroot02: test fchroot() invalid arguments
fchroot03: test fchroot() permission checks
fchroot04: test fchroot() into failfs as root
fchroot05: test failfs root can not be referenced
fchroot06: test path walks under failfs root
fchroot07: test execve blocked by failfs root
fchroot08: test failfs root fork inheritance
fchroot09: test setns escape from failfs root
fchroot10: test failfs entry without no_new_privs
fchroot11: test failfs entry with no_new_privs
fchroot12: test failfs entry with shared fs_struct
fchroot13: test failfs entry when chrooted
include/lapi/fcntl.h | 13 +++
runtest/syscalls | 14 +++
testcases/kernel/syscalls/fchroot/.gitignore | 14 +++
testcases/kernel/syscalls/fchroot/Makefile | 7 ++
testcases/kernel/syscalls/fchroot/fchroot01.c | 56 ++++++++++
testcases/kernel/syscalls/fchroot/fchroot02.c | 75 +++++++++++++
testcases/kernel/syscalls/fchroot/fchroot03.c | 70 ++++++++++++
testcases/kernel/syscalls/fchroot/fchroot04.c | 67 +++++++++++
testcases/kernel/syscalls/fchroot/fchroot05.c | 73 ++++++++++++
testcases/kernel/syscalls/fchroot/fchroot06.c | 124 +++++++++++++++++++++
testcases/kernel/syscalls/fchroot/fchroot07.c | 58 ++++++++++
.../kernel/syscalls/fchroot/fchroot07_child.c | 16 +++
testcases/kernel/syscalls/fchroot/fchroot08.c | 52 +++++++++
testcases/kernel/syscalls/fchroot/fchroot09.c | 68 +++++++++++
testcases/kernel/syscalls/fchroot/fchroot10.c | 52 +++++++++
testcases/kernel/syscalls/fchroot/fchroot11.c | 85 ++++++++++++++
testcases/kernel/syscalls/fchroot/fchroot12.c | 83 ++++++++++++++
testcases/kernel/syscalls/fchroot/fchroot13.c | 61 ++++++++++
18 files changed, 988 insertions(+)
---
base-commit: 1aae0e4d9d1e2e7818fda773b372cee8fb856de7
change-id: 20260819-fchroot-66115fd0d497
Best regards,
--
Andrea Cervesato <andrea.cervesato@suse.com>
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 24+ messages in thread
* [LTP] [PATCH v5 01/14] lapi: fallback fchroot() parameters
2026-10-20 10:16 [LTP] [PATCH v5 00/14] fchroot: add fchroot() testing suite Andrea Cervesato
@ 2026-10-20 10:16 ` Andrea Cervesato
2026-10-20 12:39 ` Cyril Hrubis
2026-10-20 14:53 ` [LTP] " linuxtestproject.agent
2026-10-20 10:16 ` [LTP] [PATCH v5 02/14] fchroot01: test fchroot() with a directory fd Andrea Cervesato
` (13 subsequent siblings)
14 siblings, 2 replies; 24+ messages in thread
From: Andrea Cervesato @ 2026-10-20 10:16 UTC (permalink / raw)
To: Linux Test Project
From: Andrea Cervesato <andrea.cervesato@suse.com>
Add to fcntl.h fallback definitions:
- FD_PIDFS_ROOT
- FD_NSFS_ROOT
- FD_FAILFS_ROOT
Signed-off-by: Andrea Cervesato <andrea.cervesato@suse.com>
---
include/lapi/fcntl.h | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/include/lapi/fcntl.h b/include/lapi/fcntl.h
index 6a9ef11ce..d70954ea7 100644
--- a/include/lapi/fcntl.h
+++ b/include/lapi/fcntl.h
@@ -144,6 +144,19 @@
# endif
#endif
+/* Roots of kernel internal pseudo filesystems, usable as fd sentinels */
+#ifndef FD_PIDFS_ROOT
+# define FD_PIDFS_ROOT -10002
+#endif
+
+#ifndef FD_NSFS_ROOT
+# define FD_NSFS_ROOT -10003
+#endif
+
+#ifndef FD_FAILFS_ROOT
+# define FD_FAILFS_ROOT -10004
+#endif
+
#ifndef FALLOC_FL_KEEP_SIZE
# define FALLOC_FL_KEEP_SIZE 1
#endif
--
2.51.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [LTP] [PATCH v5 02/14] fchroot01: test fchroot() with a directory fd
2026-10-20 10:16 [LTP] [PATCH v5 00/14] fchroot: add fchroot() testing suite Andrea Cervesato
2026-10-20 10:16 ` [LTP] [PATCH v5 01/14] lapi: fallback fchroot() parameters Andrea Cervesato
@ 2026-10-20 10:16 ` Andrea Cervesato
2026-10-20 10:16 ` [LTP] [PATCH v5 03/14] fchroot02: test fchroot() invalid arguments Andrea Cervesato
` (12 subsequent siblings)
14 siblings, 0 replies; 24+ messages in thread
From: Andrea Cervesato @ 2026-10-20 10:16 UTC (permalink / raw)
To: Linux Test Project
From: Andrea Cervesato <andrea.cervesato@suse.com>
Verify that fchroot() with a directory fd moves the process root to
the directory referenced by the fd.
Reviewed-by: Cyril Hrubis <chrubis@suse.cz>
Signed-off-by: Andrea Cervesato <andrea.cervesato@suse.com>
---
runtest/syscalls | 2 +
testcases/kernel/syscalls/fchroot/.gitignore | 1 +
testcases/kernel/syscalls/fchroot/Makefile | 7 ++++
testcases/kernel/syscalls/fchroot/fchroot01.c | 56 +++++++++++++++++++++++++++
4 files changed, 66 insertions(+)
diff --git a/runtest/syscalls b/runtest/syscalls
index accdf2df9..558ad1732 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -316,6 +316,8 @@ fchownat01 fchownat01
fchownat02 fchownat02
fchownat03 fchownat03
+fchroot01 fchroot01
+
fcntl01 fcntl01
fcntl01_64 fcntl01_64
fcntl02 fcntl02
diff --git a/testcases/kernel/syscalls/fchroot/.gitignore b/testcases/kernel/syscalls/fchroot/.gitignore
new file mode 100644
index 000000000..03ebdbe7a
--- /dev/null
+++ b/testcases/kernel/syscalls/fchroot/.gitignore
@@ -0,0 +1 @@
+fchroot01
diff --git a/testcases/kernel/syscalls/fchroot/Makefile b/testcases/kernel/syscalls/fchroot/Makefile
new file mode 100644
index 000000000..137550149
--- /dev/null
+++ b/testcases/kernel/syscalls/fchroot/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (C) 2026 SUSE LLC Andrea Cervesato <andrea.cervesato@suse.com>
+
+top_srcdir ?= ../../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/syscalls/fchroot/fchroot01.c b/testcases/kernel/syscalls/fchroot/fchroot01.c
new file mode 100644
index 000000000..c43ba4a73
--- /dev/null
+++ b/testcases/kernel/syscalls/fchroot/fchroot01.c
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 SUSE LLC Andrea Cervesato <andrea.cervesato@suse.com>
+ */
+
+/*\
+ * Test that :manpage:`fchroot(2)` with a regular directory fd moves the
+ * process root to the directory referenced by the fd.
+ *
+ * This is the fd-based counterpart of :manpage:`chroot(2)`, introduced in
+ * Linux v7.3. Root is required because :manpage:`fchroot(2)` with a regular
+ * directory fd requires ``CAP_SYS_CHROOT``.
+ *
+ * The test runs in a forked child so the root of the parent process is
+ * left untouched.
+ */
+
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "tst_test.h"
+#include "lapi/fcntl.h"
+#include "lapi/syscalls.h"
+
+#define JAILDIR "jail"
+#define CANARY "/canary"
+#define CANARYDIR (JAILDIR CANARY)
+
+static void run(void)
+{
+ struct stat st;
+
+ if (SAFE_FORK())
+ return;
+
+ int dfd = SAFE_OPEN(JAILDIR, O_PATH | O_DIRECTORY);
+
+ TST_EXP_PASS(tst_syscall(__NR_fchroot, dfd, 0),
+ "fchroot() with a directory fd");
+
+ TST_EXP_PASS(stat(CANARY, &st),
+ "canary file visible under the new root");
+}
+
+static void setup(void)
+{
+ SAFE_MKDIR(JAILDIR, 0755);
+ SAFE_TOUCH(CANARYDIR, 0644, NULL);
+}
+
+static struct tst_test test = {
+ .setup = setup,
+ .test_all = run,
+ .needs_root = 1,
+ .needs_tmpdir = 1,
+ .forks_child = 1,
+};
--
2.51.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [LTP] [PATCH v5 03/14] fchroot02: test fchroot() invalid arguments
2026-10-20 10:16 [LTP] [PATCH v5 00/14] fchroot: add fchroot() testing suite Andrea Cervesato
2026-10-20 10:16 ` [LTP] [PATCH v5 01/14] lapi: fallback fchroot() parameters Andrea Cervesato
2026-10-20 10:16 ` [LTP] [PATCH v5 02/14] fchroot01: test fchroot() with a directory fd Andrea Cervesato
@ 2026-10-20 10:16 ` Andrea Cervesato
2026-10-20 10:16 ` [LTP] [PATCH v5 04/14] fchroot03: test fchroot() permission checks Andrea Cervesato
` (11 subsequent siblings)
14 siblings, 0 replies; 24+ messages in thread
From: Andrea Cervesato @ 2026-10-20 10:16 UTC (permalink / raw)
To: Linux Test Project
From: Andrea Cervesato <andrea.cervesato@suse.com>
Verify the fchroot() error paths: a non-zero flags argument fails with
EINVAL before anything else, an invalid fd fails with EBADF, including
the FD_PIDFS_ROOT and FD_NSFS_ROOT sentinels, and a fd referring to a
regular file fails with ENOTDIR. All these checks happen before the
CAP_SYS_CHROOT check, so no privileges are needed.
Reviewed-by: Cyril Hrubis <chrubis@suse.cz>
Signed-off-by: Andrea Cervesato <andrea.cervesato@suse.com>
---
runtest/syscalls | 1 +
testcases/kernel/syscalls/fchroot/.gitignore | 1 +
testcases/kernel/syscalls/fchroot/fchroot02.c | 75 +++++++++++++++++++++++++++
3 files changed, 77 insertions(+)
diff --git a/runtest/syscalls b/runtest/syscalls
index 558ad1732..c4070e970 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -317,6 +317,7 @@ fchownat02 fchownat02
fchownat03 fchownat03
fchroot01 fchroot01
+fchroot02 fchroot02
fcntl01 fcntl01
fcntl01_64 fcntl01_64
diff --git a/testcases/kernel/syscalls/fchroot/.gitignore b/testcases/kernel/syscalls/fchroot/.gitignore
index 03ebdbe7a..fb3287612 100644
--- a/testcases/kernel/syscalls/fchroot/.gitignore
+++ b/testcases/kernel/syscalls/fchroot/.gitignore
@@ -1 +1,2 @@
fchroot01
+fchroot02
diff --git a/testcases/kernel/syscalls/fchroot/fchroot02.c b/testcases/kernel/syscalls/fchroot/fchroot02.c
new file mode 100644
index 000000000..cf5bca874
--- /dev/null
+++ b/testcases/kernel/syscalls/fchroot/fchroot02.c
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 SUSE LLC Andrea Cervesato <andrea.cervesato@suse.com>
+ */
+
+/*\
+ * Test the :manpage:`fchroot(2)` error paths for invalid arguments:
+ *
+ * - a non-zero flags argument fails with ``EINVAL``
+ * - an invalid fd fails with ``EBADF``, including the ``FD_PIDFS_ROOT`` and
+ * ``FD_NSFS_ROOT`` sentinels which :manpage:`fchroot(2)` does not accept
+ * - a fd referring to a regular file fails with ``ENOTDIR``
+ *
+ * All these checks happen before the ``CAP_SYS_CHROOT`` check, so the test
+ * needs no privileges.
+ */
+
+#include <fcntl.h>
+#include "tst_test.h"
+#include "lapi/fcntl.h"
+#include "lapi/syscalls.h"
+
+#define FILENAME "file.txt"
+
+static int dir_fd = -1;
+static int file_fd = -1;
+static int bad_fd = -1;
+static int failfs_root = FD_FAILFS_ROOT;
+static int pidfs_root = FD_PIDFS_ROOT;
+static int nsfs_root = FD_NSFS_ROOT;
+
+static struct tcase {
+ int *fd;
+ unsigned int flags;
+ int exp_errno;
+ const char *desc;
+} tcases[] = {
+ {&dir_fd, 1, EINVAL, "non-zero flags with a directory fd"},
+ {&failfs_root, 1, EINVAL, "non-zero flags with FD_FAILFS_ROOT"},
+ {&bad_fd, 0, EBADF, "invalid fd"},
+ {&pidfs_root, 0, EBADF, "FD_PIDFS_ROOT sentinel"},
+ {&nsfs_root, 0, EBADF, "FD_NSFS_ROOT sentinel"},
+ {&file_fd, 0, ENOTDIR, "fd referring to a regular file"},
+};
+
+static void run(unsigned int i)
+{
+ struct tcase *tc = &tcases[i];
+
+ TST_EXP_FAIL(tst_syscall(__NR_fchroot, *tc->fd, tc->flags),
+ tc->exp_errno, "fchroot() with %s", tc->desc);
+}
+
+static void setup(void)
+{
+ dir_fd = SAFE_OPEN(".", O_PATH | O_DIRECTORY);
+ file_fd = SAFE_OPEN(FILENAME, O_CREAT | O_EXCL | O_WRONLY, 0644);
+}
+
+static void cleanup(void)
+{
+ if (dir_fd != -1)
+ SAFE_CLOSE(dir_fd);
+
+ if (file_fd != -1)
+ SAFE_CLOSE(file_fd);
+}
+
+static struct tst_test test = {
+ .test = run,
+ .tcnt = ARRAY_SIZE(tcases),
+ .setup = setup,
+ .cleanup = cleanup,
+ .needs_tmpdir = 1,
+};
--
2.51.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [LTP] [PATCH v5 04/14] fchroot03: test fchroot() permission checks
2026-10-20 10:16 [LTP] [PATCH v5 00/14] fchroot: add fchroot() testing suite Andrea Cervesato
` (2 preceding siblings ...)
2026-10-20 10:16 ` [LTP] [PATCH v5 03/14] fchroot02: test fchroot() invalid arguments Andrea Cervesato
@ 2026-10-20 10:16 ` Andrea Cervesato
2026-10-20 10:16 ` [LTP] [PATCH v5 05/14] fchroot04: test fchroot() into failfs as root Andrea Cervesato
` (10 subsequent siblings)
14 siblings, 0 replies; 24+ messages in thread
From: Andrea Cervesato @ 2026-10-20 10:16 UTC (permalink / raw)
To: Linux Test Project
From: Andrea Cervesato <andrea.cervesato@suse.com>
Verify that with a regular directory fd the kernel first checks the
execute permission on the directory and then CAP_SYS_CHROOT: an
unprivileged process with an accessible directory fails with EPERM and
a process without execute permission fails with EACCES.
Reviewed-by: Cyril Hrubis <chrubis@suse.cz>
Signed-off-by: Andrea Cervesato <andrea.cervesato@suse.com>
---
runtest/syscalls | 1 +
testcases/kernel/syscalls/fchroot/.gitignore | 1 +
testcases/kernel/syscalls/fchroot/fchroot03.c | 70 +++++++++++++++++++++++++++
3 files changed, 72 insertions(+)
diff --git a/runtest/syscalls b/runtest/syscalls
index c4070e970..3b05e15eb 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -318,6 +318,7 @@ fchownat03 fchownat03
fchroot01 fchroot01
fchroot02 fchroot02
+fchroot03 fchroot03
fcntl01 fcntl01
fcntl01_64 fcntl01_64
diff --git a/testcases/kernel/syscalls/fchroot/.gitignore b/testcases/kernel/syscalls/fchroot/.gitignore
index fb3287612..235befd99 100644
--- a/testcases/kernel/syscalls/fchroot/.gitignore
+++ b/testcases/kernel/syscalls/fchroot/.gitignore
@@ -1,2 +1,3 @@
fchroot01
fchroot02
+fchroot03
diff --git a/testcases/kernel/syscalls/fchroot/fchroot03.c b/testcases/kernel/syscalls/fchroot/fchroot03.c
new file mode 100644
index 000000000..56ec7b8a3
--- /dev/null
+++ b/testcases/kernel/syscalls/fchroot/fchroot03.c
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 SUSE LLC Andrea Cervesato <andrea.cervesato@suse.com>
+ */
+
+/*\
+ * Test the :manpage:`fchroot(2)` permission checks with a regular directory
+ * fd:
+ *
+ * - an unprivileged process with an accessible directory fails with ``EPERM``
+ * - a process without execute permission on the directory fails with ``EACCES``
+ *
+ * Root is required to open the directory file descriptors before dropping
+ * to an unprivileged user in forked children.
+ */
+
+#include <fcntl.h>
+#include <pwd.h>
+#include "tst_test.h"
+#include "lapi/fcntl.h"
+#include "lapi/syscalls.h"
+
+static struct tcase {
+ const char *dir;
+ mode_t mode;
+ int exp_errno;
+ const char *desc;
+} tcases[] = {
+ {"pubdir", 0755, EPERM, "no CAP_SYS_CHROOT"},
+ {"privdir", 0600, EACCES, "no execute permission"},
+};
+
+static struct passwd *ltpuser;
+
+static void run(unsigned int i)
+{
+ struct tcase *tc = &tcases[i];
+
+ if (SAFE_FORK())
+ return;
+
+ int dfd = SAFE_OPEN(tc->dir, O_PATH | O_DIRECTORY);
+
+ SAFE_SETRESUID(ltpuser->pw_uid, ltpuser->pw_uid,
+ ltpuser->pw_uid);
+
+ TST_EXP_FAIL(tst_syscall(__NR_fchroot, dfd, 0),
+ tc->exp_errno, "fchroot() with %s", tc->desc);
+}
+
+static void setup(void)
+{
+ unsigned int i;
+
+ ltpuser = SAFE_GETPWNAM("nobody");
+
+ for (i = 0; i < ARRAY_SIZE(tcases); i++) {
+ SAFE_MKDIR(tcases[i].dir, tcases[i].mode);
+ SAFE_CHMOD(tcases[i].dir, tcases[i].mode);
+ }
+}
+
+static struct tst_test test = {
+ .test = run,
+ .tcnt = ARRAY_SIZE(tcases),
+ .setup = setup,
+ .needs_root = 1,
+ .needs_tmpdir = 1,
+ .forks_child = 1,
+};
--
2.51.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [LTP] [PATCH v5 05/14] fchroot04: test fchroot() into failfs as root
2026-10-20 10:16 [LTP] [PATCH v5 00/14] fchroot: add fchroot() testing suite Andrea Cervesato
` (3 preceding siblings ...)
2026-10-20 10:16 ` [LTP] [PATCH v5 04/14] fchroot03: test fchroot() permission checks Andrea Cervesato
@ 2026-10-20 10:16 ` Andrea Cervesato
2026-10-20 10:16 ` [LTP] [PATCH v5 06/14] fchroot05: test failfs root can not be referenced Andrea Cervesato
` (9 subsequent siblings)
14 siblings, 0 replies; 24+ messages in thread
From: Andrea Cervesato @ 2026-10-20 10:16 UTC (permalink / raw)
To: Linux Test Project
From: Andrea Cervesato <andrea.cervesato@suse.com>
Verify that entering failfs with the FD_FAILFS_ROOT sentinel succeeds
with CAP_SYS_CHROOT and that every absolute path lookup then fails
with EOPNOTSUPP, while the working directory is reported as
unreachable by getcwd().
Reviewed-by: Cyril Hrubis <chrubis@suse.cz>
Signed-off-by: Andrea Cervesato <andrea.cervesato@suse.com>
---
runtest/syscalls | 1 +
testcases/kernel/syscalls/fchroot/.gitignore | 1 +
testcases/kernel/syscalls/fchroot/fchroot04.c | 67 +++++++++++++++++++++++++++
3 files changed, 69 insertions(+)
diff --git a/runtest/syscalls b/runtest/syscalls
index 3b05e15eb..a7483f97b 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -319,6 +319,7 @@ fchownat03 fchownat03
fchroot01 fchroot01
fchroot02 fchroot02
fchroot03 fchroot03
+fchroot04 fchroot04
fcntl01 fcntl01
fcntl01_64 fcntl01_64
diff --git a/testcases/kernel/syscalls/fchroot/.gitignore b/testcases/kernel/syscalls/fchroot/.gitignore
index 235befd99..9270c1408 100644
--- a/testcases/kernel/syscalls/fchroot/.gitignore
+++ b/testcases/kernel/syscalls/fchroot/.gitignore
@@ -1,3 +1,4 @@
fchroot01
fchroot02
fchroot03
+fchroot04
diff --git a/testcases/kernel/syscalls/fchroot/fchroot04.c b/testcases/kernel/syscalls/fchroot/fchroot04.c
new file mode 100644
index 000000000..a087a39e6
--- /dev/null
+++ b/testcases/kernel/syscalls/fchroot/fchroot04.c
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 SUSE LLC Andrea Cervesato <andrea.cervesato@suse.com>
+ */
+
+/*\
+ * Test :manpage:`fchroot(2)` with the ``FD_FAILFS_ROOT`` sentinel as a
+ * privileged process.
+ *
+ * :manpage:`fchroot(2)` was introduced in Linux v7.3 together with failfs,
+ * a kernel internal filesystem where every operation fails with
+ * ``EOPNOTSUPP``. The ``FD_FAILFS_ROOT`` sentinel moves the process root
+ * there without needing a file descriptor: it is the ``fs_struct`` equivalent
+ * of ``RESOLVE_BENEATH``.
+ *
+ * The test verifies that entering failfs succeeds with ``CAP_SYS_CHROOT`` and
+ * that every absolute path lookup then fails with ``EOPNOTSUPP``. The working
+ * directory, left behind in the real filesystem, is consequently reported
+ * as unreachable by :manpage:`getcwd(2)`.
+ *
+ * The test runs in a forked child so the root of the parent process is
+ * left untouched.
+ */
+
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include "tst_test.h"
+#include "lapi/fcntl.h"
+#include "lapi/syscalls.h"
+
+#define UNR_TAG "(unreachable)/"
+
+static void run(void)
+{
+ if (SAFE_FORK())
+ return;
+
+ char buf[PATH_MAX];
+
+ TST_EXP_PASS(tst_syscall(__NR_fchroot, FD_FAILFS_ROOT, 0),
+ "fchroot() with the FD_FAILFS_ROOT sentinel");
+
+ TST_EXP_FAIL2(open("/dev/zero", O_RDONLY), EOPNOTSUPP,
+ "absolute file open");
+
+ TST_EXP_FAIL(mkdir("/foo", 0700), EOPNOTSUPP,
+ "absolute directory creation");
+
+ /*
+ * The libc getcwd() wrapper rejects the "(unreachable)"
+ * prefix produced by the kernel, so call the raw syscall.
+ */
+ TEST(tst_syscall(__NR_getcwd, buf, sizeof(buf)));
+ if (TST_RET > 0)
+ TST_EXP_EQ_STRN(buf, UNR_TAG, sizeof(UNR_TAG) - 1);
+ else
+ tst_res(TFAIL | TTERRNO, "getcwd() failed");
+}
+
+static struct tst_test test = {
+ .test_all = run,
+ .needs_root = 1,
+ .needs_tmpdir = 1,
+ .forks_child = 1,
+};
--
2.51.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [LTP] [PATCH v5 06/14] fchroot05: test failfs root can not be referenced
2026-10-20 10:16 [LTP] [PATCH v5 00/14] fchroot: add fchroot() testing suite Andrea Cervesato
` (4 preceding siblings ...)
2026-10-20 10:16 ` [LTP] [PATCH v5 05/14] fchroot04: test fchroot() into failfs as root Andrea Cervesato
@ 2026-10-20 10:16 ` Andrea Cervesato
2026-10-20 10:16 ` [LTP] [PATCH v5 07/14] fchroot06: test path walks under failfs root Andrea Cervesato
` (8 subsequent siblings)
14 siblings, 0 replies; 24+ messages in thread
From: Andrea Cervesato @ 2026-10-20 10:16 UTC (permalink / raw)
To: Linux Test Project
From: Andrea Cervesato <andrea.cervesato@suse.com>
Verify that once fchroot() moved the root into failfs, the root
directory can not be opened anymore, not even with O_PATH, nor pinned
by following the /proc/self/root magic link, although readlink()
still names it as "failfs:/" since it does not follow the link.
Reviewed-by: Cyril Hrubis <chrubis@suse.cz>
Signed-off-by: Andrea Cervesato <andrea.cervesato@suse.com>
---
runtest/syscalls | 1 +
testcases/kernel/syscalls/fchroot/.gitignore | 1 +
testcases/kernel/syscalls/fchroot/fchroot05.c | 73 +++++++++++++++++++++++++++
3 files changed, 75 insertions(+)
diff --git a/runtest/syscalls b/runtest/syscalls
index a7483f97b..a5ed6792e 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -320,6 +320,7 @@ fchroot01 fchroot01
fchroot02 fchroot02
fchroot03 fchroot03
fchroot04 fchroot04
+fchroot05 fchroot05
fcntl01 fcntl01
fcntl01_64 fcntl01_64
diff --git a/testcases/kernel/syscalls/fchroot/.gitignore b/testcases/kernel/syscalls/fchroot/.gitignore
index 9270c1408..0697f10eb 100644
--- a/testcases/kernel/syscalls/fchroot/.gitignore
+++ b/testcases/kernel/syscalls/fchroot/.gitignore
@@ -2,3 +2,4 @@ fchroot01
fchroot02
fchroot03
fchroot04
+fchroot05
diff --git a/testcases/kernel/syscalls/fchroot/fchroot05.c b/testcases/kernel/syscalls/fchroot/fchroot05.c
new file mode 100644
index 000000000..77cb6952b
--- /dev/null
+++ b/testcases/kernel/syscalls/fchroot/fchroot05.c
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 SUSE LLC Andrea Cervesato <andrea.cervesato@suse.com>
+ */
+
+/*\
+ * Test that the failfs root can not be referenced once it is the process
+ * root.
+ *
+ * After :manpage:`fchroot(2)` moved the root into failfs, the root
+ * directory can not be opened anymore, not even with ``O_PATH``, because the
+ * walk lands on the failfs root as its terminal. The root also can not
+ * be pinned by following the /proc/self/root magic link into it, although
+ * :manpage:`readlink(2)` still names it as "failfs:/" since it does not
+ * follow the link.
+ *
+ * Root is required because entering failfs with the ``FD_FAILFS_ROOT``
+ * sentinel requires ``CAP_SYS_CHROOT``.
+ *
+ * /proc must be opened before entering failfs because every absolute path
+ * lookup fails once the root is unreachable. The test runs in a forked
+ * child so the root of the parent process is left untouched.
+ */
+
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include "tst_test.h"
+#include "lapi/fcntl.h"
+#include "lapi/syscalls.h"
+
+static void run(void)
+{
+ if (SAFE_FORK())
+ return;
+
+ char buf[PATH_MAX];
+ struct stat st;
+ int procfd, len;
+
+ procfd = SAFE_OPEN("/proc", O_PATH | O_DIRECTORY);
+
+ TST_EXP_PASS(tst_syscall(__NR_fchroot, FD_FAILFS_ROOT, 0),
+ "fchroot() with the FD_FAILFS_ROOT sentinel");
+
+ TST_EXP_FAIL2(open("/", O_RDONLY | O_DIRECTORY), EOPNOTSUPP,
+ "open() of the failfs root");
+
+ TST_EXP_FAIL2(open("/", O_PATH), EOPNOTSUPP,
+ "O_PATH open() of the failfs root");
+
+ TST_EXP_FAIL2(openat(procfd, "self/root", O_PATH), EOPNOTSUPP,
+ "pin of the root via /proc/self/root");
+
+ TST_EXP_FAIL(fstatat(procfd, "self/root", &st, 0), EOPNOTSUPP,
+ "stat of the root via /proc/self/root");
+
+ len = readlinkat(procfd, "self/root", buf, sizeof(buf) - 1);
+ if (len < 0) {
+ tst_res(TFAIL | TTERRNO, "readlinkat() of /proc/self/root");
+ } else {
+ buf[len] = '\0';
+ TST_EXP_EQ_STR(buf, "failfs:/");
+ }
+}
+
+static struct tst_test test = {
+ .test_all = run,
+ .needs_root = 1,
+ .forks_child = 1,
+};
--
2.51.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [LTP] [PATCH v5 07/14] fchroot06: test path walks under failfs root
2026-10-20 10:16 [LTP] [PATCH v5 00/14] fchroot: add fchroot() testing suite Andrea Cervesato
` (5 preceding siblings ...)
2026-10-20 10:16 ` [LTP] [PATCH v5 06/14] fchroot05: test failfs root can not be referenced Andrea Cervesato
@ 2026-10-20 10:16 ` Andrea Cervesato
2026-10-20 13:02 ` Cyril Hrubis
2026-10-20 10:16 ` [LTP] [PATCH v5 08/14] fchroot07: test execve blocked by " Andrea Cervesato
` (7 subsequent siblings)
14 siblings, 1 reply; 24+ messages in thread
From: Andrea Cervesato @ 2026-10-20 10:16 UTC (permalink / raw)
To: Linux Test Project
From: Andrea Cervesato <andrea.cervesato@suse.com>
Verify that once the root is in failfs only lookups anchored at a
file descriptor keep working: relative lookups from the working
directory and from a pre-opened directory fd succeed, absolute
symlinks fail with EOPNOTSUPP and ".." walks clamp at the top of the
mount tree, landing on the real root.
Signed-off-by: Andrea Cervesato <andrea.cervesato@suse.com>
---
runtest/syscalls | 1 +
testcases/kernel/syscalls/fchroot/.gitignore | 1 +
testcases/kernel/syscalls/fchroot/fchroot06.c | 124 ++++++++++++++++++++++++++
3 files changed, 126 insertions(+)
diff --git a/runtest/syscalls b/runtest/syscalls
index a5ed6792e..8b688cbe2 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -321,6 +321,7 @@ fchroot02 fchroot02
fchroot03 fchroot03
fchroot04 fchroot04
fchroot05 fchroot05
+fchroot06 fchroot06
fcntl01 fcntl01
fcntl01_64 fcntl01_64
diff --git a/testcases/kernel/syscalls/fchroot/.gitignore b/testcases/kernel/syscalls/fchroot/.gitignore
index 0697f10eb..12151270a 100644
--- a/testcases/kernel/syscalls/fchroot/.gitignore
+++ b/testcases/kernel/syscalls/fchroot/.gitignore
@@ -3,3 +3,4 @@ fchroot02
fchroot03
fchroot04
fchroot05
+fchroot06
diff --git a/testcases/kernel/syscalls/fchroot/fchroot06.c b/testcases/kernel/syscalls/fchroot/fchroot06.c
new file mode 100644
index 000000000..d7d7dfc50
--- /dev/null
+++ b/testcases/kernel/syscalls/fchroot/fchroot06.c
@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 SUSE LLC Andrea Cervesato <andrea.cervesato@suse.com>
+ */
+
+/*\
+ * Test path walks under the failfs root.
+ *
+ * Once :manpage:`fchroot(2)` moved the process root into failfs, only
+ * lookups anchored at a file descriptor keep working:
+ *
+ * - lookups relative to the working directory, which stays in the real
+ * filesystem, keep working
+ * - lookups anchored at a pre-opened directory fd keep working, including
+ * resolution of relative symlinks
+ * - absolute symlinks restart the walk at the failfs root and fail with
+ * ``EOPNOTSUPP``
+ * - ".." walks clamp at the top of the mount tree, not at the failfs
+ * root, so walking up from the working directory lands on the real
+ * root
+ *
+ * Root is required because entering failfs with the ``FD_FAILFS_ROOT``
+ * sentinel requires ``CAP_SYS_CHROOT``.
+ *
+ * The test runs in a forked child so the root of the parent process is
+ * left untouched.
+ */
+
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <string.h>
+#include <sys/stat.h>
+#include "tst_test.h"
+#include "lapi/fcntl.h"
+#include "lapi/syscalls.h"
+
+#define RELDIR "rel"
+#define ABSDIR "abs"
+
+static char upwards[PATH_MAX];
+
+static void run(void)
+{
+ if (SAFE_FORK())
+ return;
+
+ struct stat realroot, st;
+ int dfd, fd;
+
+ SAFE_STAT("/", &realroot);
+ dfd = SAFE_OPEN(".", O_RDONLY | O_DIRECTORY);
+
+ TST_EXP_PASS(tst_syscall(__NR_fchroot, FD_FAILFS_ROOT, 0),
+ "fchroot() with the FD_FAILFS_ROOT sentinel");
+
+ fd = TST_EXP_FD(openat(AT_FDCWD, ".", O_RDONLY | O_DIRECTORY));
+ if (fd != -1)
+ SAFE_CLOSE(fd);
+
+ fd = TST_EXP_FD(openat(dfd, "canary", O_WRONLY | O_CREAT, 0600));
+ if (fd != -1) {
+ SAFE_WRITE(SAFE_WRITE_ALL, fd, "x", 1);
+ SAFE_CLOSE(fd);
+ }
+
+ fd = TST_EXP_FD(openat(dfd, RELDIR, O_RDONLY));
+ if (fd != -1)
+ SAFE_CLOSE(fd);
+
+ TST_EXP_FAIL2(openat(dfd, ABSDIR, O_RDONLY), EOPNOTSUPP,
+ "resolution of an absolute symlink");
+
+ fd = TST_EXP_FD(openat(AT_FDCWD, upwards, O_PATH));
+ if (fd != -1) {
+ SAFE_FSTAT(fd, &st);
+ SAFE_CLOSE(fd);
+
+ TST_EXP_EXPR(st.st_dev == realroot.st_dev &&
+ st.st_ino == realroot.st_ino,
+ "'..' walk clamps at the top of the mount tree");
+ }
+
+ SAFE_CLOSE(dfd);
+}
+
+static void setup(void)
+{
+ char *tmpdir;
+ char abs_path[PATH_MAX];
+ struct stat root_st, st;
+ int off;
+
+ tmpdir = tst_tmpdir_path();
+ snprintf(abs_path, sizeof(abs_path), "%s/%s", tmpdir, "target");
+
+ SAFE_TOUCH("target", 0644, NULL);
+ SAFE_SYMLINK("target", RELDIR);
+ SAFE_SYMLINK(abs_path, ABSDIR);
+
+ SAFE_STAT("/", &root_st);
+
+ strcpy(upwards, "..");
+ off = 2;
+ while (1) {
+ SAFE_STAT(upwards, &st);
+
+ if (st.st_dev == root_st.st_dev && st.st_ino == root_st.st_ino)
+ break;
+
+ if ((size_t)off + sizeof("/..") > sizeof(upwards))
+ tst_brk(TBROK, "Path to root too long");
+
+ strcpy(upwards + off, "/..");
+ off += 3;
+ }
+}
+
+static struct tst_test test = {
+ .setup = setup,
+ .test_all = run,
+ .needs_root = 1,
+ .needs_tmpdir = 1,
+ .forks_child = 1,
+};
--
2.51.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [LTP] [PATCH v5 08/14] fchroot07: test execve blocked by failfs root
2026-10-20 10:16 [LTP] [PATCH v5 00/14] fchroot: add fchroot() testing suite Andrea Cervesato
` (6 preceding siblings ...)
2026-10-20 10:16 ` [LTP] [PATCH v5 07/14] fchroot06: test path walks under failfs root Andrea Cervesato
@ 2026-10-20 10:16 ` Andrea Cervesato
2026-10-20 10:16 ` [LTP] [PATCH v5 09/14] fchroot08: test failfs root fork inheritance Andrea Cervesato
` (6 subsequent siblings)
14 siblings, 0 replies; 24+ messages in thread
From: Andrea Cervesato @ 2026-10-20 10:16 UTC (permalink / raw)
To: Linux Test Project
From: Andrea Cervesato <andrea.cervesato@suse.com>
Verify that after fchroot() moved the root into failfs, loading a
binary by absolute path fails with EOPNOTSUPP. The exec runs in a
grandchild so a wrongly successful exec is still detected through
the exit code.
Reviewed-by: Cyril Hrubis <chrubis@suse.cz>
Signed-off-by: Andrea Cervesato <andrea.cervesato@suse.com>
---
runtest/syscalls | 1 +
testcases/kernel/syscalls/fchroot/.gitignore | 2 +
testcases/kernel/syscalls/fchroot/fchroot07.c | 58 ++++++++++++++++++++++
.../kernel/syscalls/fchroot/fchroot07_child.c | 16 ++++++
4 files changed, 77 insertions(+)
diff --git a/runtest/syscalls b/runtest/syscalls
index 8b688cbe2..bc2dffdca 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -322,6 +322,7 @@ fchroot03 fchroot03
fchroot04 fchroot04
fchroot05 fchroot05
fchroot06 fchroot06
+fchroot07 fchroot07
fcntl01 fcntl01
fcntl01_64 fcntl01_64
diff --git a/testcases/kernel/syscalls/fchroot/.gitignore b/testcases/kernel/syscalls/fchroot/.gitignore
index 12151270a..bfa2a8ce1 100644
--- a/testcases/kernel/syscalls/fchroot/.gitignore
+++ b/testcases/kernel/syscalls/fchroot/.gitignore
@@ -4,3 +4,5 @@ fchroot03
fchroot04
fchroot05
fchroot06
+fchroot07
+fchroot07_child
diff --git a/testcases/kernel/syscalls/fchroot/fchroot07.c b/testcases/kernel/syscalls/fchroot/fchroot07.c
new file mode 100644
index 000000000..0bd16890d
--- /dev/null
+++ b/testcases/kernel/syscalls/fchroot/fchroot07.c
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 SUSE LLC Andrea Cervesato <andrea.cervesato@suse.com>
+ */
+
+/*\
+ * Test that :manpage:`execve(2)` is blocked under the failfs root.
+ *
+ * After :manpage:`fchroot(2)` moved the process root into failfs, loading
+ * a binary by absolute path fails with ``EOPNOTSUPP``.
+ *
+ * Root is required because entering failfs with the ``FD_FAILFS_ROOT``
+ * sentinel requires ``CAP_SYS_CHROOT``.
+ *
+ * The test runs in a forked child so the root of the parent process is
+ * left untouched.
+ */
+
+#define _GNU_SOURCE
+#include <unistd.h>
+#include "tst_test.h"
+#include "lapi/fcntl.h"
+#include "lapi/syscalls.h"
+
+#define CHILD_EXEC "fchroot07_child"
+
+static char child_path[PATH_MAX];
+
+static void run(void)
+{
+ if (SAFE_FORK())
+ return;
+
+ TST_EXP_PASS(tst_syscall(__NR_fchroot, FD_FAILFS_ROOT, 0),
+ "fchroot() with the FD_FAILFS_ROOT sentinel");
+
+ if (!SAFE_FORK()) {
+ TST_EXP_FAIL(execl(child_path, CHILD_EXEC, NULL), EOPNOTSUPP,
+ "absolute exec blocked by the failfs root");
+ return;
+ }
+
+ SAFE_WAIT(NULL);
+}
+
+static void setup(void)
+{
+ if (tst_get_path(CHILD_EXEC, child_path, sizeof(child_path)))
+ tst_brk(TCONF, "Couldn't find %s in $PATH", CHILD_EXEC);
+}
+
+static struct tst_test test = {
+ .setup = setup,
+ .test_all = run,
+ .needs_root = 1,
+ .forks_child = 1,
+ .child_needs_reinit = 1,
+};
diff --git a/testcases/kernel/syscalls/fchroot/fchroot07_child.c b/testcases/kernel/syscalls/fchroot/fchroot07_child.c
new file mode 100644
index 000000000..3d731781c
--- /dev/null
+++ b/testcases/kernel/syscalls/fchroot/fchroot07_child.c
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 SUSE LLC Andrea Cervesato <andrea.cervesato@suse.com>
+ */
+
+#define TST_NO_DEFAULT_MAIN
+#include "tst_test.h"
+
+int main(void)
+{
+ tst_reinit();
+
+ tst_res(TFAIL, "child executed");
+
+ return 0;
+}
--
2.51.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [LTP] [PATCH v5 09/14] fchroot08: test failfs root fork inheritance
2026-10-20 10:16 [LTP] [PATCH v5 00/14] fchroot: add fchroot() testing suite Andrea Cervesato
` (7 preceding siblings ...)
2026-10-20 10:16 ` [LTP] [PATCH v5 08/14] fchroot07: test execve blocked by " Andrea Cervesato
@ 2026-10-20 10:16 ` Andrea Cervesato
2026-10-20 13:05 ` Cyril Hrubis
2026-10-20 10:16 ` [LTP] [PATCH v5 10/14] fchroot09: test setns escape from failfs root Andrea Cervesato
` (5 subsequent siblings)
14 siblings, 1 reply; 24+ messages in thread
From: Andrea Cervesato @ 2026-10-20 10:16 UTC (permalink / raw)
To: Linux Test Project
From: Andrea Cervesato <andrea.cervesato@suse.com>
Verify that the root moved into failfs by fchroot() lives in the
fs_struct which is duplicated on fork, so a child of a process with
the failfs root also fails every absolute path lookup with
EOPNOTSUPP.
Signed-off-by: Andrea Cervesato <andrea.cervesato@suse.com>
---
runtest/syscalls | 1 +
testcases/kernel/syscalls/fchroot/.gitignore | 1 +
testcases/kernel/syscalls/fchroot/fchroot08.c | 52 +++++++++++++++++++++++++++
3 files changed, 54 insertions(+)
diff --git a/runtest/syscalls b/runtest/syscalls
index bc2dffdca..24d9b1b4a 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -323,6 +323,7 @@ fchroot04 fchroot04
fchroot05 fchroot05
fchroot06 fchroot06
fchroot07 fchroot07
+fchroot08 fchroot08
fcntl01 fcntl01
fcntl01_64 fcntl01_64
diff --git a/testcases/kernel/syscalls/fchroot/.gitignore b/testcases/kernel/syscalls/fchroot/.gitignore
index bfa2a8ce1..0a2510d8a 100644
--- a/testcases/kernel/syscalls/fchroot/.gitignore
+++ b/testcases/kernel/syscalls/fchroot/.gitignore
@@ -6,3 +6,4 @@ fchroot05
fchroot06
fchroot07
fchroot07_child
+fchroot08
diff --git a/testcases/kernel/syscalls/fchroot/fchroot08.c b/testcases/kernel/syscalls/fchroot/fchroot08.c
new file mode 100644
index 000000000..1b20e3162
--- /dev/null
+++ b/testcases/kernel/syscalls/fchroot/fchroot08.c
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 SUSE LLC Andrea Cervesato <andrea.cervesato@suse.com>
+ */
+
+/*\
+ * Test that the failfs root is inherited across :manpage:`fork(2)`.
+ *
+ * The root moved into failfs by :manpage:`fchroot(2)` lives in the
+ * fs_struct which is duplicated on :manpage:`fork(2)`, so a child of a
+ * process with the failfs root also fails every absolute path lookup with
+ * ``EOPNOTSUPP``.
+ *
+ * Root is required because entering failfs with the ``FD_FAILFS_ROOT``
+ * sentinel requires ``CAP_SYS_CHROOT``.
+ *
+ * The test runs in a forked child so the root of the parent process is
+ * left untouched.
+ */
+
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <sys/wait.h>
+#include "tst_test.h"
+#include "lapi/fcntl.h"
+#include "lapi/syscalls.h"
+
+static void run(void)
+{
+ if (SAFE_FORK())
+ return;
+
+ pid_t pid;
+
+ TST_EXP_PASS(tst_syscall(__NR_fchroot, FD_FAILFS_ROOT, 0),
+ "fchroot() with the FD_FAILFS_ROOT sentinel");
+
+ pid = SAFE_FORK();
+ if (!pid) {
+ TST_EXP_FAIL2(open("/", O_PATH), EOPNOTSUPP,
+ "absolute lookup in a forked child");
+ return;
+ }
+
+ SAFE_WAITPID(pid, NULL, 0);
+}
+
+static struct tst_test test = {
+ .test_all = run,
+ .needs_root = 1,
+ .forks_child = 1,
+};
--
2.51.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [LTP] [PATCH v5 10/14] fchroot09: test setns escape from failfs root
2026-10-20 10:16 [LTP] [PATCH v5 00/14] fchroot: add fchroot() testing suite Andrea Cervesato
` (8 preceding siblings ...)
2026-10-20 10:16 ` [LTP] [PATCH v5 09/14] fchroot08: test failfs root fork inheritance Andrea Cervesato
@ 2026-10-20 10:16 ` Andrea Cervesato
2026-10-20 15:26 ` Cyril Hrubis
2026-10-20 10:16 ` [LTP] [PATCH v5 11/14] fchroot10: test failfs entry without no_new_privs Andrea Cervesato
` (4 subsequent siblings)
14 siblings, 1 reply; 24+ messages in thread
From: Andrea Cervesato @ 2026-10-20 10:16 UTC (permalink / raw)
To: Linux Test Project
From: Andrea Cervesato <andrea.cervesato@suse.com>
Verify that entering failfs with fchroot() is hard to undo: a process
inside counts as chrooted, and one way out is a pre-opened mount
namespace fd. setns() into it resets both the root and the working
directory.
Signed-off-by: Andrea Cervesato <andrea.cervesato@suse.com>
---
runtest/syscalls | 1 +
testcases/kernel/syscalls/fchroot/.gitignore | 1 +
testcases/kernel/syscalls/fchroot/fchroot09.c | 68 +++++++++++++++++++++++++++
3 files changed, 70 insertions(+)
diff --git a/runtest/syscalls b/runtest/syscalls
index 24d9b1b4a..b4723fee4 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -324,6 +324,7 @@ fchroot05 fchroot05
fchroot06 fchroot06
fchroot07 fchroot07
fchroot08 fchroot08
+fchroot09 fchroot09
fcntl01 fcntl01
fcntl01_64 fcntl01_64
diff --git a/testcases/kernel/syscalls/fchroot/.gitignore b/testcases/kernel/syscalls/fchroot/.gitignore
index 0a2510d8a..b577da61f 100644
--- a/testcases/kernel/syscalls/fchroot/.gitignore
+++ b/testcases/kernel/syscalls/fchroot/.gitignore
@@ -7,3 +7,4 @@ fchroot06
fchroot07
fchroot07_child
fchroot08
+fchroot09
diff --git a/testcases/kernel/syscalls/fchroot/fchroot09.c b/testcases/kernel/syscalls/fchroot/fchroot09.c
new file mode 100644
index 000000000..f4f0e1100
--- /dev/null
+++ b/testcases/kernel/syscalls/fchroot/fchroot09.c
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 SUSE LLC Andrea Cervesato <andrea.cervesato@suse.com>
+ */
+
+/*\
+ * Test that :manpage:`setns(2)` escapes the failfs root.
+ *
+ * Entering failfs with :manpage:`fchroot(2)` is hard to undo: a process
+ * inside counts as chrooted, so :manpage:`chroot(2)` and :manpage:`fchroot(2)`
+ * back out require ``CAP_SYS_CHROOT``. One way out is a pre-opened mount
+ * namespace file descriptor: :manpage:`setns(2)` into it resets both the
+ * root and the working directory.
+ *
+ * Root is required because entering failfs requires ``CAP_SYS_CHROOT`` and
+ * :manpage:`setns(2)` into the mount namespace requires ``CAP_SYS_ADMIN``.
+ *
+ * The test runs in a forked child so the root of the parent process is
+ * left untouched.
+ */
+
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <sys/stat.h>
+#include "tst_test.h"
+#include "lapi/fcntl.h"
+#include "lapi/sched.h"
+#include "lapi/setns.h"
+#include "lapi/syscalls.h"
+
+static void run(void)
+{
+ if (SAFE_FORK())
+ return;
+
+ struct stat realroot, st;
+ int nsfd;
+
+ SAFE_STAT("/", &realroot);
+ nsfd = SAFE_OPEN("/proc/self/ns/mnt", O_RDONLY);
+
+ TST_EXP_PASS(tst_syscall(__NR_fchroot, FD_FAILFS_ROOT, 0),
+ "fchroot() with the FD_FAILFS_ROOT sentinel");
+
+ TST_EXP_FAIL2(open("/", O_PATH), EOPNOTSUPP,
+ "absolute lookup after entering failfs");
+
+ TST_EXP_PASS(setns(nsfd, CLONE_NEWNS),
+ "setns() back into the mount namespace");
+
+ SAFE_CLOSE(nsfd);
+
+ SAFE_STAT("/", &st);
+ TST_EXP_EXPR(st.st_dev == realroot.st_dev &&
+ st.st_ino == realroot.st_ino,
+ "root restored after setns()");
+
+ SAFE_STAT(".", &st);
+ TST_EXP_EXPR(st.st_dev == realroot.st_dev &&
+ st.st_ino == realroot.st_ino,
+ "working directory restored after setns()");
+}
+
+static struct tst_test test = {
+ .test_all = run,
+ .needs_root = 1,
+ .forks_child = 1,
+};
--
2.51.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [LTP] [PATCH v5 11/14] fchroot10: test failfs entry without no_new_privs
2026-10-20 10:16 [LTP] [PATCH v5 00/14] fchroot: add fchroot() testing suite Andrea Cervesato
` (9 preceding siblings ...)
2026-10-20 10:16 ` [LTP] [PATCH v5 10/14] fchroot09: test setns escape from failfs root Andrea Cervesato
@ 2026-10-20 10:16 ` Andrea Cervesato
2026-10-20 10:16 ` [LTP] [PATCH v5 12/14] fchroot11: test failfs entry with no_new_privs Andrea Cervesato
` (3 subsequent siblings)
14 siblings, 0 replies; 24+ messages in thread
From: Andrea Cervesato @ 2026-10-20 10:16 UTC (permalink / raw)
To: Linux Test Project
From: Andrea Cervesato <andrea.cervesato@suse.com>
Verify that unprivileged fchroot() into failfs is refused without
no_new_privs: without it a setuid binary on a regular mount is still
reachable via an inherited directory fd, and executing it with an
unusable root directory is the classic confused deputy, so the kernel
refuses the syscall with EPERM.
Reviewed-by: Cyril Hrubis <chrubis@suse.cz>
Signed-off-by: Andrea Cervesato <andrea.cervesato@suse.com>
---
runtest/syscalls | 1 +
testcases/kernel/syscalls/fchroot/.gitignore | 1 +
testcases/kernel/syscalls/fchroot/fchroot10.c | 52 +++++++++++++++++++++++++++
3 files changed, 54 insertions(+)
diff --git a/runtest/syscalls b/runtest/syscalls
index b4723fee4..429e2740c 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -325,6 +325,7 @@ fchroot06 fchroot06
fchroot07 fchroot07
fchroot08 fchroot08
fchroot09 fchroot09
+fchroot10 fchroot10
fcntl01 fcntl01
fcntl01_64 fcntl01_64
diff --git a/testcases/kernel/syscalls/fchroot/.gitignore b/testcases/kernel/syscalls/fchroot/.gitignore
index b577da61f..53a1fa1d0 100644
--- a/testcases/kernel/syscalls/fchroot/.gitignore
+++ b/testcases/kernel/syscalls/fchroot/.gitignore
@@ -8,3 +8,4 @@ fchroot07
fchroot07_child
fchroot08
fchroot09
+fchroot10
diff --git a/testcases/kernel/syscalls/fchroot/fchroot10.c b/testcases/kernel/syscalls/fchroot/fchroot10.c
new file mode 100644
index 000000000..202e536b3
--- /dev/null
+++ b/testcases/kernel/syscalls/fchroot/fchroot10.c
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 SUSE LLC Andrea Cervesato <andrea.cervesato@suse.com>
+ */
+
+/*\
+ * Test that unprivileged :manpage:`fchroot(2)` into failfs is refused
+ * without no_new_privs.
+ *
+ * Without no_new_privs a setuid binary on a regular mount is still
+ * reachable via an inherited directory file descriptor, and executing it
+ * with an unusable root directory is the classic confused deputy, so the
+ * kernel refuses the syscall with ``EPERM``.
+ *
+ * Root is required to drop to an unprivileged user in the forked child.
+ */
+
+#define _GNU_SOURCE
+#include <pwd.h>
+#include "tst_test.h"
+#include "lapi/fcntl.h"
+#include "lapi/prctl.h"
+#include "lapi/syscalls.h"
+
+static struct passwd *ltpuser;
+
+static void run(void)
+{
+ if (SAFE_FORK())
+ return;
+
+ SAFE_SETRESUID(ltpuser->pw_uid, ltpuser->pw_uid,
+ ltpuser->pw_uid);
+
+ TST_EXP_FAIL(tst_syscall(__NR_fchroot, FD_FAILFS_ROOT, 0),
+ EPERM, "unprivileged fchroot() without no_new_privs");
+}
+
+static void setup(void)
+{
+ ltpuser = SAFE_GETPWNAM("nobody");
+
+ if (SAFE_PRCTL(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0))
+ tst_brk(TCONF, "PR_GET_NO_NEW_PRIVS is already set");
+}
+
+static struct tst_test test = {
+ .setup = setup,
+ .test_all = run,
+ .needs_root = 1,
+ .forks_child = 1,
+};
--
2.51.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [LTP] [PATCH v5 12/14] fchroot11: test failfs entry with no_new_privs
2026-10-20 10:16 [LTP] [PATCH v5 00/14] fchroot: add fchroot() testing suite Andrea Cervesato
` (10 preceding siblings ...)
2026-10-20 10:16 ` [LTP] [PATCH v5 11/14] fchroot10: test failfs entry without no_new_privs Andrea Cervesato
@ 2026-10-20 10:16 ` Andrea Cervesato
2026-10-20 15:41 ` Cyril Hrubis
2026-10-20 10:16 ` [LTP] [PATCH v5 13/14] fchroot12: test failfs entry with shared fs_struct Andrea Cervesato
` (2 subsequent siblings)
14 siblings, 1 reply; 24+ messages in thread
From: Andrea Cervesato @ 2026-10-20 10:16 UTC (permalink / raw)
To: Linux Test Project
From: Andrea Cervesato <andrea.cervesato@suse.com>
Verify that an unprivileged process may enter failfs when it
committed to no_new_privs, since setuid binaries then pose no
confused deputy risk anymore. The test also verifies that a process
which entered failfs counts as chrooted: it can no longer create a
user namespace with unshare() to regain CAP_SYS_CHROOT.
Signed-off-by: Andrea Cervesato <andrea.cervesato@suse.com>
---
runtest/syscalls | 1 +
testcases/kernel/syscalls/fchroot/.gitignore | 1 +
testcases/kernel/syscalls/fchroot/fchroot11.c | 85 +++++++++++++++++++++++++++
3 files changed, 87 insertions(+)
diff --git a/runtest/syscalls b/runtest/syscalls
index 429e2740c..bd7b352fd 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -326,6 +326,7 @@ fchroot07 fchroot07
fchroot08 fchroot08
fchroot09 fchroot09
fchroot10 fchroot10
+fchroot11 fchroot11
fcntl01 fcntl01
fcntl01_64 fcntl01_64
diff --git a/testcases/kernel/syscalls/fchroot/.gitignore b/testcases/kernel/syscalls/fchroot/.gitignore
index 53a1fa1d0..650ef2690 100644
--- a/testcases/kernel/syscalls/fchroot/.gitignore
+++ b/testcases/kernel/syscalls/fchroot/.gitignore
@@ -9,3 +9,4 @@ fchroot07_child
fchroot08
fchroot09
fchroot10
+fchroot11
diff --git a/testcases/kernel/syscalls/fchroot/fchroot11.c b/testcases/kernel/syscalls/fchroot/fchroot11.c
new file mode 100644
index 000000000..03aaed886
--- /dev/null
+++ b/testcases/kernel/syscalls/fchroot/fchroot11.c
@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 SUSE LLC Andrea Cervesato <andrea.cervesato@suse.com>
+ */
+
+/*\
+ * Test unprivileged :manpage:`fchroot(2)` into failfs with no_new_privs
+ * set.
+ *
+ * An unprivileged process may enter failfs when it committed to
+ * no_new_privs, since setuid binaries then pose no confused deputy risk
+ * anymore. The test also verifies that a process which entered failfs
+ * counts as chrooted: it can no longer create a user namespace with
+ * :manpage:`unshare(2)` to regain ``CAP_SYS_CHROOT``.
+ *
+ * The user namespace check requires ``CONFIG_USER_NS``, which is enforced
+ * with ``.needs_kconfigs``.
+ *
+ * Root is required to drop to an unprivileged user in the forked child.
+ */
+
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <pwd.h>
+#include <sched.h>
+#include <sys/wait.h>
+#include "tst_test.h"
+#include "lapi/fcntl.h"
+#include "lapi/prctl.h"
+#include "lapi/sched.h"
+#include "lapi/syscalls.h"
+
+static struct passwd *ltpuser;
+
+static void run(void)
+{
+ int status;
+ pid_t pid;
+
+ if (SAFE_FORK())
+ return;
+
+ SAFE_SETRESUID(ltpuser->pw_uid, ltpuser->pw_uid,
+ ltpuser->pw_uid);
+
+ SAFE_PRCTL(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
+
+ pid = SAFE_FORK();
+ if (!pid) {
+ if (unshare(CLONE_NEWUSER) == -1)
+ exit(1);
+ return;
+ }
+
+ SAFE_WAITPID(pid, &status, 0);
+
+ TST_EXP_PASS(tst_syscall(__NR_fchroot, FD_FAILFS_ROOT, 0),
+ "unprivileged fchroot() with no_new_privs");
+
+ TST_EXP_FAIL2(open("/dev/zero", O_RDONLY), EOPNOTSUPP,
+ "absolute lookup after entering failfs");
+
+ if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
+ TST_EXP_FAIL(unshare(CLONE_NEWUSER), EPERM,
+ "user namespace creation blocked by the failfs root");
+ } else {
+ tst_res(TCONF, "unprivileged user namespaces are not supported");
+ }
+}
+
+static void setup(void)
+{
+ ltpuser = SAFE_GETPWNAM("nobody");
+}
+
+static struct tst_test test = {
+ .setup = setup,
+ .test_all = run,
+ .needs_root = 1,
+ .forks_child = 1,
+ .needs_kconfigs = (const char *[]) {
+ "CONFIG_USER_NS=y",
+ NULL,
+ }
+};
--
2.51.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [LTP] [PATCH v5 13/14] fchroot12: test failfs entry with shared fs_struct
2026-10-20 10:16 [LTP] [PATCH v5 00/14] fchroot: add fchroot() testing suite Andrea Cervesato
` (11 preceding siblings ...)
2026-10-20 10:16 ` [LTP] [PATCH v5 12/14] fchroot11: test failfs entry with no_new_privs Andrea Cervesato
@ 2026-10-20 10:16 ` Andrea Cervesato
2026-10-20 10:16 ` [LTP] [PATCH v5 14/14] fchroot13: test failfs entry when chrooted Andrea Cervesato
2026-10-20 16:13 ` [LTP] [PATCH v5 00/14] fchroot: add fchroot() testing suite Andrea Cervesato via ltp
14 siblings, 0 replies; 24+ messages in thread
From: Andrea Cervesato @ 2026-10-20 10:16 UTC (permalink / raw)
To: Linux Test Project
From: Andrea Cervesato <andrea.cervesato@suse.com>
Verify that unprivileged fchroot() into failfs is refused with a
shared fs_struct: a CLONE_FS sibling without no_new_privs could
execute a setuid binary with the failfs root, so entry requires
fs->users == 1, the same restriction setns() applies for the mount
namespace. The sibling bumps fs->users to 2, making the syscall fail
with EINVAL.
Reviewed-by: Cyril Hrubis <chrubis@suse.cz>
Signed-off-by: Andrea Cervesato <andrea.cervesato@suse.com>
---
runtest/syscalls | 1 +
testcases/kernel/syscalls/fchroot/.gitignore | 1 +
testcases/kernel/syscalls/fchroot/fchroot12.c | 83 +++++++++++++++++++++++++++
3 files changed, 85 insertions(+)
diff --git a/runtest/syscalls b/runtest/syscalls
index bd7b352fd..bd3bbff4a 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -327,6 +327,7 @@ fchroot08 fchroot08
fchroot09 fchroot09
fchroot10 fchroot10
fchroot11 fchroot11
+fchroot12 fchroot12
fcntl01 fcntl01
fcntl01_64 fcntl01_64
diff --git a/testcases/kernel/syscalls/fchroot/.gitignore b/testcases/kernel/syscalls/fchroot/.gitignore
index 650ef2690..e8a6730ee 100644
--- a/testcases/kernel/syscalls/fchroot/.gitignore
+++ b/testcases/kernel/syscalls/fchroot/.gitignore
@@ -10,3 +10,4 @@ fchroot08
fchroot09
fchroot10
fchroot11
+fchroot12
diff --git a/testcases/kernel/syscalls/fchroot/fchroot12.c b/testcases/kernel/syscalls/fchroot/fchroot12.c
new file mode 100644
index 000000000..ebf92a741
--- /dev/null
+++ b/testcases/kernel/syscalls/fchroot/fchroot12.c
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 SUSE LLC Andrea Cervesato <andrea.cervesato@suse.com>
+ */
+
+/*\
+ * Test that unprivileged :manpage:`fchroot(2)` into failfs is refused
+ * with a shared ``fs_struct``.
+ *
+ * ``no_new_privs`` is checked on the calling thread, but the root lives in
+ * the fs_struct shared with a :manpage:`clone(2)` ``CLONE_FS`` sibling. A
+ * sibling without no_new_privs could execute a setuid binary with the
+ * failfs root, so entry requires ``fs->users == 1``, the same restriction
+ * :manpage:`setns(2)` applies for the mount namespace. The sibling bumps
+ * ``fs->users`` to 2, making the syscall fail with ``EINVAL``.
+ *
+ * Root is required to drop to an unprivileged user in the forked child.
+ */
+
+#define _GNU_SOURCE
+#include <sys/wait.h>
+#include <pwd.h>
+#include <sched.h>
+#include <signal.h>
+#include <unistd.h>
+#include "tst_test.h"
+#include "lapi/fcntl.h"
+#include "lapi/prctl.h"
+#include "lapi/sched.h"
+#include "lapi/syscalls.h"
+
+static struct passwd *ltpuser;
+
+static void run(void)
+{
+ if (SAFE_FORK())
+ return;
+
+ struct tst_clone_args args = {
+ .flags = CLONE_FS,
+ .exit_signal = SIGCHLD,
+ };
+ pid_t parent = getpid();
+ pid_t sib;
+
+ SAFE_SETRESUID(ltpuser->pw_uid, ltpuser->pw_uid,
+ ltpuser->pw_uid);
+
+ sib = SAFE_CLONE(&args);
+ if (!sib) {
+ /*
+ * Parked sibling sharing the fs_struct with the
+ * caller. It must die with the parent, otherwise it
+ * would outlive the test.
+ */
+ SAFE_PRCTL(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
+ if (getppid() == parent)
+ pause();
+ exit(0);
+ }
+
+ SAFE_PRCTL(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
+
+ TST_EXP_FAIL(tst_syscall(__NR_fchroot, FD_FAILFS_ROOT, 0),
+ EINVAL, "fchroot() with a shared fs_struct");
+
+ SAFE_KILL(sib, SIGKILL);
+ SAFE_WAITPID(sib, NULL, 0);
+}
+
+static void setup(void)
+{
+ clone3_supported_by_kernel();
+
+ ltpuser = SAFE_GETPWNAM("nobody");
+}
+
+static struct tst_test test = {
+ .setup = setup,
+ .test_all = run,
+ .needs_root = 1,
+ .forks_child = 1,
+};
--
2.51.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [LTP] [PATCH v5 14/14] fchroot13: test failfs entry when chrooted
2026-10-20 10:16 [LTP] [PATCH v5 00/14] fchroot: add fchroot() testing suite Andrea Cervesato
` (12 preceding siblings ...)
2026-10-20 10:16 ` [LTP] [PATCH v5 13/14] fchroot12: test failfs entry with shared fs_struct Andrea Cervesato
@ 2026-10-20 10:16 ` Andrea Cervesato
2026-10-20 15:44 ` Cyril Hrubis
2026-10-20 16:13 ` [LTP] [PATCH v5 00/14] fchroot: add fchroot() testing suite Andrea Cervesato via ltp
14 siblings, 1 reply; 24+ messages in thread
From: Andrea Cervesato @ 2026-10-20 10:16 UTC (permalink / raw)
To: Linux Test Project
From: Andrea Cervesato <andrea.cervesato@suse.com>
Verify that unprivileged fchroot() into failfs is refused when the
process is already chrooted: moving the root of a chrooted task into
failfs would allow it to escape its chroot via openat(fd, "..") with
a pre-opened directory fd, so the kernel refuses the syscall with
EPERM.
Signed-off-by: Andrea Cervesato <andrea.cervesato@suse.com>
---
runtest/syscalls | 1 +
testcases/kernel/syscalls/fchroot/.gitignore | 1 +
testcases/kernel/syscalls/fchroot/fchroot13.c | 61 +++++++++++++++++++++++++++
3 files changed, 63 insertions(+)
diff --git a/runtest/syscalls b/runtest/syscalls
index bd3bbff4a..2bdb4a9a2 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -328,6 +328,7 @@ fchroot09 fchroot09
fchroot10 fchroot10
fchroot11 fchroot11
fchroot12 fchroot12
+fchroot13 fchroot13
fcntl01 fcntl01
fcntl01_64 fcntl01_64
diff --git a/testcases/kernel/syscalls/fchroot/.gitignore b/testcases/kernel/syscalls/fchroot/.gitignore
index e8a6730ee..3e77e3762 100644
--- a/testcases/kernel/syscalls/fchroot/.gitignore
+++ b/testcases/kernel/syscalls/fchroot/.gitignore
@@ -11,3 +11,4 @@ fchroot09
fchroot10
fchroot11
fchroot12
+fchroot13
diff --git a/testcases/kernel/syscalls/fchroot/fchroot13.c b/testcases/kernel/syscalls/fchroot/fchroot13.c
new file mode 100644
index 000000000..ea361ff6a
--- /dev/null
+++ b/testcases/kernel/syscalls/fchroot/fchroot13.c
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 SUSE LLC Andrea Cervesato <andrea.cervesato@suse.com>
+ */
+
+/*\
+ * Test that unprivileged :manpage:`fchroot(2)` into failfs is refused
+ * when the process is already chrooted.
+ *
+ * The root directory is what confines ".." resolution and the failfs root
+ * can never be reached by walking up a real mount tree. Moving the root
+ * of a chrooted task into failfs would allow it to escape its chroot via
+ * :manpage:`openat(2)` with ".." and a pre-opened directory file descriptor,
+ * so the kernel refuses the syscall with ``EPERM``.
+ *
+ * Root is required to create the chroot jail before dropping to an
+ * unprivileged user in the forked child.
+ */
+
+#define _GNU_SOURCE
+#include <pwd.h>
+#include <unistd.h>
+#include "tst_test.h"
+#include "lapi/fcntl.h"
+#include "lapi/prctl.h"
+#include "lapi/syscalls.h"
+
+#define JAILDIR "jail"
+
+static struct passwd *ltpuser;
+
+static void run(void)
+{
+ if (SAFE_FORK())
+ return;
+
+ SAFE_CHROOT(JAILDIR);
+ SAFE_CHDIR("/");
+
+ SAFE_SETRESUID(ltpuser->pw_uid, ltpuser->pw_uid,
+ ltpuser->pw_uid);
+
+ SAFE_PRCTL(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
+
+ TST_EXP_FAIL(tst_syscall(__NR_fchroot, FD_FAILFS_ROOT, 0),
+ EPERM, "fchroot() from a chrooted process");
+}
+
+static void setup(void)
+{
+ ltpuser = SAFE_GETPWNAM("nobody");
+ SAFE_MKDIR(JAILDIR, 0755);
+}
+
+static struct tst_test test = {
+ .setup = setup,
+ .test_all = run,
+ .needs_root = 1,
+ .needs_tmpdir = 1,
+ .forks_child = 1,
+};
--
2.51.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [LTP] [PATCH v5 01/14] lapi: fallback fchroot() parameters
2026-10-20 10:16 ` [LTP] [PATCH v5 01/14] lapi: fallback fchroot() parameters Andrea Cervesato
@ 2026-10-20 12:39 ` Cyril Hrubis
2026-10-20 14:53 ` [LTP] " linuxtestproject.agent
1 sibling, 0 replies; 24+ messages in thread
From: Cyril Hrubis @ 2026-10-20 12:39 UTC (permalink / raw)
To: Andrea Cervesato; +Cc: Linux Test Project
Hi!
Reviewed-by: Cyril Hrubis <chrubis@suse.cz>
--
Cyril Hrubis
chrubis@suse.cz
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [LTP] [PATCH v5 07/14] fchroot06: test path walks under failfs root
2026-10-20 10:16 ` [LTP] [PATCH v5 07/14] fchroot06: test path walks under failfs root Andrea Cervesato
@ 2026-10-20 13:02 ` Cyril Hrubis
0 siblings, 0 replies; 24+ messages in thread
From: Cyril Hrubis @ 2026-10-20 13:02 UTC (permalink / raw)
To: Andrea Cervesato; +Cc: Linux Test Project
Hi!
Reviewed-by: Cyril Hrubis <chrubis@suse.cz>
--
Cyril Hrubis
chrubis@suse.cz
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [LTP] [PATCH v5 09/14] fchroot08: test failfs root fork inheritance
2026-10-20 10:16 ` [LTP] [PATCH v5 09/14] fchroot08: test failfs root fork inheritance Andrea Cervesato
@ 2026-10-20 13:05 ` Cyril Hrubis
0 siblings, 0 replies; 24+ messages in thread
From: Cyril Hrubis @ 2026-10-20 13:05 UTC (permalink / raw)
To: Andrea Cervesato; +Cc: Linux Test Project
Hi!
Reviewed-by: Cyril Hrubis <chrubis@suse.cz>
--
Cyril Hrubis
chrubis@suse.cz
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [LTP] lapi: fallback fchroot() parameters
2026-10-20 10:16 ` [LTP] [PATCH v5 01/14] lapi: fallback fchroot() parameters Andrea Cervesato
2026-10-20 12:39 ` Cyril Hrubis
@ 2026-10-20 14:53 ` linuxtestproject.agent
2026-10-20 15:57 ` Andrea Cervesato via ltp
1 sibling, 1 reply; 24+ messages in thread
From: linuxtestproject.agent @ 2026-10-20 14:53 UTC (permalink / raw)
To: Andrea Cervesato; +Cc: ltp
Hi Andrea,
On 2026-10-20, Andrea Cervesato wrote:
> lapi: fallback fchroot() parameters
--- [PATCH 5/14] ---
> diff --git a/testcases/kernel/syscalls/fchroot/fchroot04.c b/testcases/kernel/syscalls/fchroot/fchroot04.c
> + TST_EXP_PASS(tst_syscall(__NR_fchroot, FD_FAILFS_ROOT, 0),
> + "fchroot() with the FD_FAILFS_ROOT sentinel");
High: Stop the child if this expectation fails; TST_EXP_PASS records a
failure but continues execution. For example, root without CAP_SYS_CHROOT
and without no_new_privs gets EPERM, then mkdir("/foo") can create a
directory in the real root that tmpdir cleanup never removes. Check
TST_PASS and exit before executing the absolute-path operations.
--- [PATCH 6/14] ---
> diff --git a/testcases/kernel/syscalls/fchroot/fchroot05.c b/testcases/kernel/syscalls/fchroot/fchroot05.c
> + len = readlinkat(procfd, "self/root", buf, sizeof(buf) - 1);
> + if (len < 0) {
> + tst_res(TFAIL | TTERRNO, "readlinkat() of /proc/self/root");
Low: TTERRNO still reads the preceding fstatat() error from TST_ERR,
not readlinkat()'s errno. Wrap readlinkat() in TEST() or
TST_EXP_POSITIVE() so a failure reports the actual error.
--- [PATCH 8/14] ---
> diff --git a/testcases/kernel/syscalls/fchroot/fchroot07.c b/testcases/kernel/syscalls/fchroot/fchroot07.c
> + SAFE_WAIT(NULL);
High: A successfully executed helper can fail in tst_reinit() while
reopening its absolute IPC pathname under failfs, before it can publish
TFAIL through shared memory. SAFE_WAIT(NULL) discards that nonzero exit,
leaving only the earlier fchroot TPASS and a false pass. Use
tst_reap_children() to retain child-failure detection.
> + if (tst_get_path(CHILD_EXEC, child_path, sizeof(child_path)))
> + tst_brk(TCONF, "Couldn't find %s in $PATH", CHILD_EXEC);
Medium: Make child_path absolute before entering failfs. Despite its API
comment, tst_get_path() returns ./fchroot07_child when the helper is found
through an empty or relative PATH component, including the empty component
inserted by the framework when running from the build directory. This
tests relative execution and, for a dynamic binary, its interpreter lookup
rather than the claimed absolute executable lookup.
--- [PATCH 12/14] ---
> diff --git a/testcases/kernel/syscalls/fchroot/fchroot11.c b/testcases/kernel/syscalls/fchroot/fchroot11.c
> + .needs_kconfigs = (const char *[]) {
> + "CONFIG_USER_NS=y",
> + NULL,
> + }
Medium: Gate only the user-namespace subcase, not the entire test.
Unprivileged failfs entry with no_new_privs works without CONFIG_USER_NS,
but this descriptor skips its success and lookup checks as well. Keep
those checks enabled and report TCONF only for the unavailable namespace
subcase.
Verdict - Needs revision
Pre-existing issue: include/tst_fs.h documents tst_get_path() as returning
an absolute path, but lib/get_path.c preserves relative PATH entries.
---
Note:
The agent can sometimes produce false positives although often its
findings are genuine. If you find issues with the review, please
comment this email or ignore the suggestions.
Regards,
LTP AI Reviewer
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [LTP] [PATCH v5 10/14] fchroot09: test setns escape from failfs root
2026-10-20 10:16 ` [LTP] [PATCH v5 10/14] fchroot09: test setns escape from failfs root Andrea Cervesato
@ 2026-10-20 15:26 ` Cyril Hrubis
0 siblings, 0 replies; 24+ messages in thread
From: Cyril Hrubis @ 2026-10-20 15:26 UTC (permalink / raw)
To: Andrea Cervesato; +Cc: Linux Test Project
Hi!
Reviewed-by: Cyril Hrubis <chrubis@suse.cz>
--
Cyril Hrubis
chrubis@suse.cz
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [LTP] [PATCH v5 12/14] fchroot11: test failfs entry with no_new_privs
2026-10-20 10:16 ` [LTP] [PATCH v5 12/14] fchroot11: test failfs entry with no_new_privs Andrea Cervesato
@ 2026-10-20 15:41 ` Cyril Hrubis
0 siblings, 0 replies; 24+ messages in thread
From: Cyril Hrubis @ 2026-10-20 15:41 UTC (permalink / raw)
To: Andrea Cervesato; +Cc: Linux Test Project
Hi!
> +static void run(void)
> +{
> + int status;
> + pid_t pid;
> +
> + if (SAFE_FORK())
> + return;
> +
> + SAFE_SETRESUID(ltpuser->pw_uid, ltpuser->pw_uid,
> + ltpuser->pw_uid);
> +
> + SAFE_PRCTL(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
> +
> + pid = SAFE_FORK();
> + if (!pid) {
> + if (unshare(CLONE_NEWUSER) == -1)
> + exit(1);
> + return;
It would be a bit cleaner to do the unpriviledged user namespace
support probe in setup. But that is minor.
Reviewed-by: Cyril Hrubis <chrubis@suse.cz>
--
Cyril Hrubis
chrubis@suse.cz
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [LTP] [PATCH v5 14/14] fchroot13: test failfs entry when chrooted
2026-10-20 10:16 ` [LTP] [PATCH v5 14/14] fchroot13: test failfs entry when chrooted Andrea Cervesato
@ 2026-10-20 15:44 ` Cyril Hrubis
0 siblings, 0 replies; 24+ messages in thread
From: Cyril Hrubis @ 2026-10-20 15:44 UTC (permalink / raw)
To: Andrea Cervesato; +Cc: Linux Test Project
Hi!
Reviewed-by: Cyril Hrubis <chrubis@suse.cz>
--
Cyril Hrubis
chrubis@suse.cz
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [LTP] lapi: fallback fchroot() parameters
2026-10-20 14:53 ` [LTP] " linuxtestproject.agent
@ 2026-10-20 15:57 ` Andrea Cervesato via ltp
0 siblings, 0 replies; 24+ messages in thread
From: Andrea Cervesato via ltp @ 2026-10-20 15:57 UTC (permalink / raw)
To: linuxtestproject.agent; +Cc: ltp
> Hi Andrea,
>
> On 2026-10-20, Andrea Cervesato wrote:
> > lapi: fallback fchroot() parameters
>
> --- [PATCH 5/14] ---
>
> > diff --git a/testcases/kernel/syscalls/fchroot/fchroot04.c b/testcases/kernel/syscalls/fchroot/fchroot04.c
> > + TST_EXP_PASS(tst_syscall(__NR_fchroot, FD_FAILFS_ROOT, 0),
> > + "fchroot() with the FD_FAILFS_ROOT sentinel");
>
> High: Stop the child if this expectation fails; TST_EXP_PASS records a
> failure but continues execution. For example, root without CAP_SYS_CHROOT
> and without no_new_privs gets EPERM, then mkdir("/foo") can create a
> directory in the real root that tmpdir cleanup never removes. Check
> TST_PASS and exit before executing the absolute-path operations.
Valid
>
> --- [PATCH 6/14] ---
>
> > diff --git a/testcases/kernel/syscalls/fchroot/fchroot05.c b/testcases/kernel/syscalls/fchroot/fchroot05.c
> > + len = readlinkat(procfd, "self/root", buf, sizeof(buf) - 1);
> > + if (len < 0) {
> > + tst_res(TFAIL | TTERRNO, "readlinkat() of /proc/self/root");
>
> Low: TTERRNO still reads the preceding fstatat() error from TST_ERR,
> not readlinkat()'s errno. Wrap readlinkat() in TEST() or
> TST_EXP_POSITIVE() so a failure reports the actual error.
>
Valid
> --- [PATCH 8/14] ---
>
> > diff --git a/testcases/kernel/syscalls/fchroot/fchroot07.c b/testcases/kernel/syscalls/fchroot/fchroot07.c
> > + SAFE_WAIT(NULL);
>
> High: A successfully executed helper can fail in tst_reinit() while
> reopening its absolute IPC pathname under failfs, before it can publish
> TFAIL through shared memory. SAFE_WAIT(NULL) discards that nonzero exit,
> leaving only the earlier fchroot TPASS and a false pass. Use
> tst_reap_children() to retain child-failure detection.
>
> > + if (tst_get_path(CHILD_EXEC, child_path, sizeof(child_path)))
> > + tst_brk(TCONF, "Couldn't find %s in $PATH", CHILD_EXEC);
>
> Medium: Make child_path absolute before entering failfs. Despite its API
> comment, tst_get_path() returns ./fchroot07_child when the helper is found
> through an empty or relative PATH component, including the empty component
> inserted by the framework when running from the build directory. This
> tests relative execution and, for a dynamic binary, its interpreter lookup
> rather than the claimed absolute executable lookup.
All failfs assumptions don't make any sense, let's skip these.
>
> --- [PATCH 12/14] ---
>
> > diff --git a/testcases/kernel/syscalls/fchroot/fchroot11.c b/testcases/kernel/syscalls/fchroot/fchroot11.c
> > + .needs_kconfigs = (const char *[]) {
> > + "CONFIG_USER_NS=y",
> > + NULL,
> > + }
>
> Medium: Gate only the user-namespace subcase, not the entire test.
> Unprivileged failfs entry with no_new_privs works without CONFIG_USER_NS,
> but this descriptor skips its success and lookup checks as well. Keep
> those checks enabled and report TCONF only for the unavailable namespace
> subcase.
Valid
>
> Verdict - Needs revision
>
> Pre-existing issue: include/tst_fs.h documents tst_get_path() as returning
> an absolute path, but lib/get_path.c preserves relative PATH entries.
This is a documentation bug
--
Andrea Cervesato
SUSE QE Automation Engineer Linux
andrea.cervesato@suse.com
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [LTP] [PATCH v5 00/14] fchroot: add fchroot() testing suite
2026-10-20 10:16 [LTP] [PATCH v5 00/14] fchroot: add fchroot() testing suite Andrea Cervesato
` (13 preceding siblings ...)
2026-10-20 10:16 ` [LTP] [PATCH v5 14/14] fchroot13: test failfs entry when chrooted Andrea Cervesato
@ 2026-10-20 16:13 ` Andrea Cervesato via ltp
14 siblings, 0 replies; 24+ messages in thread
From: Andrea Cervesato via ltp @ 2026-10-20 16:13 UTC (permalink / raw)
To: Andrea Cervesato; +Cc: Linux Test Project
With suggested changes:
Merged, Thanks!
--
Andrea Cervesato
SUSE QE Automation Engineer Linux
andrea.cervesato@suse.com
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 24+ messages in thread
end of thread, other threads:[~2026-10-20 16:14 UTC | newest]
Thread overview: 24+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-20 10:16 [LTP] [PATCH v5 00/14] fchroot: add fchroot() testing suite Andrea Cervesato
2026-10-20 10:16 ` [LTP] [PATCH v5 01/14] lapi: fallback fchroot() parameters Andrea Cervesato
2026-10-20 12:39 ` Cyril Hrubis
2026-10-20 14:53 ` [LTP] " linuxtestproject.agent
2026-10-20 15:57 ` Andrea Cervesato via ltp
2026-10-20 10:16 ` [LTP] [PATCH v5 02/14] fchroot01: test fchroot() with a directory fd Andrea Cervesato
2026-10-20 10:16 ` [LTP] [PATCH v5 03/14] fchroot02: test fchroot() invalid arguments Andrea Cervesato
2026-10-20 10:16 ` [LTP] [PATCH v5 04/14] fchroot03: test fchroot() permission checks Andrea Cervesato
2026-10-20 10:16 ` [LTP] [PATCH v5 05/14] fchroot04: test fchroot() into failfs as root Andrea Cervesato
2026-10-20 10:16 ` [LTP] [PATCH v5 06/14] fchroot05: test failfs root can not be referenced Andrea Cervesato
2026-10-20 10:16 ` [LTP] [PATCH v5 07/14] fchroot06: test path walks under failfs root Andrea Cervesato
2026-10-20 13:02 ` Cyril Hrubis
2026-10-20 10:16 ` [LTP] [PATCH v5 08/14] fchroot07: test execve blocked by " Andrea Cervesato
2026-10-20 10:16 ` [LTP] [PATCH v5 09/14] fchroot08: test failfs root fork inheritance Andrea Cervesato
2026-10-20 13:05 ` Cyril Hrubis
2026-10-20 10:16 ` [LTP] [PATCH v5 10/14] fchroot09: test setns escape from failfs root Andrea Cervesato
2026-10-20 15:26 ` Cyril Hrubis
2026-10-20 10:16 ` [LTP] [PATCH v5 11/14] fchroot10: test failfs entry without no_new_privs Andrea Cervesato
2026-10-20 10:16 ` [LTP] [PATCH v5 12/14] fchroot11: test failfs entry with no_new_privs Andrea Cervesato
2026-10-20 15:41 ` Cyril Hrubis
2026-10-20 10:16 ` [LTP] [PATCH v5 13/14] fchroot12: test failfs entry with shared fs_struct Andrea Cervesato
2026-10-20 10:16 ` [LTP] [PATCH v5 14/14] fchroot13: test failfs entry when chrooted Andrea Cervesato
2026-10-20 15:44 ` Cyril Hrubis
2026-10-20 16:13 ` [LTP] [PATCH v5 00/14] fchroot: add fchroot() testing suite Andrea Cervesato via ltp
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox