From: "Günther Noack" <gnoack@google.com>
To: "Mickaël Salaün" <mic@digikod.net>,
"Christian Brauner" <brauner@kernel.org>
Cc: linux-security-module@vger.kernel.org,
"Paul Moore" <paul@paul-moore.com>,
"Amir Goldstein" <amir73il@gmail.com>,
"Miklos Szeredi" <miklos@szeredi.hu>,
"Serge Hallyn" <serge@hallyn.com>,
"Stephen Smalley" <stephen.smalley.work@gmail.com>,
"Günther Noack" <gnoack@google.com>
Subject: [PATCH v6 3/6] selftests/landlock: Add tests for whiteout object creation
Date: Thu, 13 Aug 2026 11:31:54 +0200 [thread overview]
Message-ID: <20260813093157.1436894-4-gnoack@google.com> (raw)
In-Reply-To: <20260813093157.1436894-1-gnoack@google.com>
Add a test to check that whiteout object creation is guarded by
LANDLOCK_ACCESS_FS_MAKE_REG, in the cases where these are created from
userspace:
* Conventional creation with mknod()
* Linking or renaming an existing whiteout object
* renameat2() with RENAME_WHITEOUT,
which creates a new whiteout object in the source location
Signed-off-by: Günther Noack <gnoack@google.com>
---
tools/testing/selftests/landlock/fs_test.c | 176 +++++++++++++++++++++
1 file changed, 176 insertions(+)
diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index e82b56a74c5f..871a5b819b98 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -2247,6 +2247,170 @@ TEST_F_FORK(layout1, rename_file)
RENAME_EXCHANGE));
}
+TEST_F_FORK(layout1, rename_whiteout_denied)
+{
+ /* The affected file is a FIFO. */
+ ASSERT_EQ(0, unlink(file1_s3d3));
+ ASSERT_EQ(0, mknod(file1_s3d3, S_IFIFO | 0600, 0));
+
+ /* Deny MAKE_REG, but allow MAKE_FIFO. */
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_MAKE_REG, NULL);
+
+ /*
+ * Try to rename a file with RENAME_WHITEOUT.
+ * file1_s3d3 is in dir_s3d2 (tmpfs), so it supports RENAME_WHITEOUT.
+ * Denied, because whiteout creation is guarded with MAKE_REG.
+ */
+ EXPECT_EQ(-1, renameat2(AT_FDCWD, file1_s3d3, AT_FDCWD,
+ TMP_DIR "/s3d1/s3d2/s3d3/f2", RENAME_WHITEOUT));
+ EXPECT_EQ(EACCES, errno);
+}
+
+static bool is_whiteout(const char *const path)
+{
+ struct stat st;
+
+ if (stat(path, &st) == -1)
+ return false;
+
+ return S_ISCHR(st.st_mode) && st.st_rdev == makedev(0, 0);
+}
+
+static bool is_fifo(const char *const path)
+{
+ struct stat st;
+
+ return stat(path, &st) == 0 && S_ISFIFO(st.st_mode);
+}
+
+TEST_F_FORK(layout1, rename_whiteout_allowed)
+{
+ const struct rule rules[] = {
+ {
+ .path = dir_s3d3,
+ .access = LANDLOCK_ACCESS_FS_MAKE_REG,
+ },
+ {},
+ };
+
+ /* The affected file is a FIFO. */
+ ASSERT_EQ(0, unlink(file1_s3d3));
+ ASSERT_EQ(0, mknod(file1_s3d3, S_IFIFO | 0600, 0));
+
+ /* Allow MAKE_REG below dir_s3d3. */
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_MAKE_REG, rules);
+
+ /*
+ * Rename a file with RENAME_WHITEOUT within the same directory.
+ * Allowed, because MAKE_REG is granted for the whiteout object which
+ * gets created in the source location.
+ */
+ EXPECT_EQ(0, renameat2(AT_FDCWD, file1_s3d3, AT_FDCWD,
+ TMP_DIR "/s3d1/s3d2/s3d3/f2", RENAME_WHITEOUT));
+
+ /* A whiteout object took the place of the moved FIFO. */
+ EXPECT_TRUE(is_whiteout(file1_s3d3));
+ EXPECT_TRUE(is_fifo(TMP_DIR "/s3d1/s3d2/s3d3/f2"));
+}
+
+TEST_F_FORK(layout1, rename_whiteout_reparenting)
+{
+ const struct rule rules[] = {
+ {
+ .path = dir_s3d2,
+ .access = LANDLOCK_ACCESS_FS_REFER,
+ },
+ {
+ .path = dir_s3d3,
+ .access = LANDLOCK_ACCESS_FS_MAKE_REG,
+ },
+ {},
+ };
+
+ /* The moved files are FIFOs. */
+ ASSERT_EQ(0, unlink(file1_s3d3));
+ ASSERT_EQ(0, mknod(file1_s3d3, S_IFIFO | 0600, 0));
+ ASSERT_EQ(0, unlink(file1_s3d4));
+ ASSERT_EQ(0, mknod(file1_s3d4, S_IFIFO | 0600, 0));
+
+ /* Allow REFER below dir_s3d2, but MAKE_REG only below dir_s3d3. */
+ enforce_fs(_metadata,
+ LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER,
+ rules);
+
+ /*
+ * The whiteout object is created in the source directory: Moving the
+ * FIFO out of dir_s3d4 is denied because MAKE_REG is not granted
+ * there, even though it is granted in the destination directory
+ * dir_s3d3.
+ */
+ EXPECT_EQ(-1, renameat2(AT_FDCWD, file1_s3d4, AT_FDCWD,
+ TMP_DIR "/s3d1/s3d2/s3d3/f2", RENAME_WHITEOUT));
+ EXPECT_EQ(EACCES, errno);
+
+ /*
+ * Moving the FIFO out of dir_s3d3 is allowed, because MAKE_REG is
+ * granted there for the created whiteout object.
+ */
+ EXPECT_EQ(0, renameat2(AT_FDCWD, file1_s3d3, AT_FDCWD,
+ TMP_DIR "/s3d1/s3d2/s3d4/f2", RENAME_WHITEOUT));
+
+ /* A whiteout object took the place of the moved FIFO. */
+ EXPECT_TRUE(is_whiteout(file1_s3d3));
+ EXPECT_TRUE(is_fifo(TMP_DIR "/s3d1/s3d2/s3d4/f2"));
+}
+
+TEST_F_FORK(layout1, rename_whiteout_exchange)
+{
+ const char *const whiteout_s3d3 = TMP_DIR "/s3d1/s3d2/s3d3/f2";
+ const struct rule rules[] = {
+ {
+ .path = dir_s3d2,
+ .access = LANDLOCK_ACCESS_FS_REFER,
+ },
+ {
+ .path = dir_s3d3,
+ .access = LANDLOCK_ACCESS_FS_MAKE_REG,
+ },
+ {},
+ };
+
+ /* The exchanged files are FIFOs and an existing whiteout object. */
+ ASSERT_EQ(0, unlink(file1_s3d3));
+ ASSERT_EQ(0, mknod(file1_s3d3, S_IFIFO | 0600, 0));
+ ASSERT_EQ(0, mknod(whiteout_s3d3, S_IFCHR | 0600, makedev(0, 0)));
+ ASSERT_EQ(0, unlink(file1_s3d4));
+ ASSERT_EQ(0, mknod(file1_s3d4, S_IFIFO | 0600, 0));
+
+ /* Allow REFER below dir_s3d2, but MAKE_REG only below dir_s3d3. */
+ enforce_fs(_metadata,
+ LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER,
+ rules);
+
+ /*
+ * With RENAME_EXCHANGE, the whiteout object moves into the source
+ * directory of the rename: Exchanging the FIFO in dir_s3d4 with the
+ * whiteout object is denied because MAKE_REG is not granted in
+ * dir_s3d4, even though it is granted in the whiteout object's own
+ * directory dir_s3d3.
+ */
+ EXPECT_EQ(-1, renameat2(AT_FDCWD, file1_s3d4, AT_FDCWD, whiteout_s3d3,
+ RENAME_EXCHANGE));
+ EXPECT_EQ(EACCES, errno);
+
+ /*
+ * Exchanging the FIFO in dir_s3d3 with the whiteout object is
+ * allowed, because MAKE_REG is granted in the directory into which
+ * the whiteout object moves.
+ */
+ EXPECT_EQ(0, renameat2(AT_FDCWD, file1_s3d3, AT_FDCWD, whiteout_s3d3,
+ RENAME_EXCHANGE));
+
+ /* The FIFO and the whiteout object swapped places. */
+ EXPECT_TRUE(is_whiteout(file1_s3d3));
+ EXPECT_TRUE(is_fifo(whiteout_s3d3));
+}
+
TEST_F_FORK(layout1, rename_dir)
{
const struct rule rules[] = {
@@ -3270,6 +3434,18 @@ TEST_F_FORK(layout1, make_char)
makedev(1, 3));
}
+TEST_F_FORK(layout1, make_whiteout)
+{
+ /*
+ * Creates a whiteout object (creation guarded by MAKE_REG).
+ *
+ * Contrary to the other character devices, this does not require
+ * CAP_MKNOD, cf. vfs_mknod().
+ */
+ test_make_file(_metadata, LANDLOCK_ACCESS_FS_MAKE_REG, S_IFCHR,
+ makedev(0, 0));
+}
+
TEST_F_FORK(layout1, make_block)
{
/* Creates a /dev/loop0 device. */
--
2.55.0.699.gb54405d56f-goog
next prev parent reply other threads:[~2026-08-13 9:32 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-13 9:31 [PATCH v6 0/6] landlock: Restrict whiteout object creation Günther Noack
2026-08-13 9:31 ` [PATCH v6 1/6] selftests/landlock: Use an actual chardev for MAKE_CHAR audit test Günther Noack
2026-08-13 9:31 ` [PATCH v6 2/6] landlock: Require LANDLOCK_ACCESS_FS_MAKE_REG for whiteout creation Günther Noack
2026-08-13 9:31 ` Günther Noack [this message]
2026-08-13 11:54 ` [PATCH v6 3/6] selftests/landlock: Add tests for whiteout object creation Günther Noack
2026-08-13 9:31 ` [PATCH v6 4/6] selftests/landlock: Add audit test " Günther Noack
2026-08-13 9:31 ` [PATCH v6 5/6] selftests/landlock: Test whiteout object behaviour in OverlayFS renames Günther Noack
2026-08-13 9:31 ` [PATCH v6 6/6] landlock: Link the erratum documentation for whiteout objects Günther Noack
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260813093157.1436894-4-gnoack@google.com \
--to=gnoack@google.com \
--cc=amir73il@gmail.com \
--cc=brauner@kernel.org \
--cc=linux-security-module@vger.kernel.org \
--cc=mic@digikod.net \
--cc=miklos@szeredi.hu \
--cc=paul@paul-moore.com \
--cc=serge@hallyn.com \
--cc=stephen.smalley.work@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is 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.