All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v6 0/6] landlock: Restrict whiteout object creation
@ 2026-08-13  9:31 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
                   ` (5 more replies)
  0 siblings, 6 replies; 8+ messages in thread
From: Günther Noack @ 2026-08-13  9:31 UTC (permalink / raw)
  To: Mickaël Salaün, Christian Brauner
  Cc: linux-security-module, Paul Moore, Amir Goldstein, Miklos Szeredi,
	Serge Hallyn, Stephen Smalley, Günther Noack

Hello!

As discussed in [1], the renameat2() syscall's RENAME_WHITEOUT flag allows
the creation of chardev directory entries with major=minor=0 as "whiteout
objects" in the location of the rename source file [2].

This functionality is available even without having any OverlayFS mounted
and can be invoked with the regular renameat2(2) syscall [3].

In V1 [5], it was discussed that whiteout objects are not the same as character
devices, and should therefore be treated differently.  After considering a new
access right in V2 and V3, we eventually settled on guarding it with the
existing LANDLOCK_ACCESS_FS_MAKE_REG access right and introducing a new erratum
[6].

V6 addresses the remaining review feedback: the V4 feedback on the selftests
in patches 3/5 and 4/5, and the V5 remarks on the get_dentry_access() helper.

Motivation
==========

The RENAME_WHITEOUT flag side-steps all of the existing Landlock access
rights, which are designed to restrict the creation of directory entries.
It is desirable to restrict that.

This patch set fixes that by adding a check in Landlock's path_rename and
path_mknod hooks.  Where we previously treated whiteout creation like the
creation of a character device, we now guard it with
LANDLOCK_ACCESS_FS_MAKE_REG and add an erratum to probe for that.


[1] https://lore.kernel.org/all/adUBCQXrt7kmgqJT@google.com/
[2] https://docs.kernel.org/filesystems/overlayfs.html#whiteouts-and-opaque-directories
[3] https://man7.org/linux/man-pages/man2/renameat2.2.html#DESCRIPTION
[4] https://codesearch.debian.net/search?q=rename.*RENAME_WHITEOUT&literal=0
[5] https://lore.kernel.org/all/20260411090944.3131168-2-gnoack@google.com/
[6] https://lore.kernel.org/all/20260720.chow9ohYie5b@digikod.net/

Changelog
=========

v6:
 - Smaller code changes:
   - get_dentry_access(): Drop the incorrect __attribute_const__ and use a
     local inode variable
 - Fix a LANDLOCK_ACCESS_FS_MAKE_REG typo in the commit message of patch 2/5
 - Tests, addressing the remaining v4 feedback on patch 3/5:
   - Add rename_whiteout_allowed: RENAME_WHITEOUT works when MAKE_REG is
     granted, and the whiteout object shows up in the source location
   - Add rename_whiteout_reparenting: the MAKE_REG right for the created
     whiteout object is required in the source directory of the rename
   - Add rename_whiteout_exchange: RENAME_EXCHANGE of an existing whiteout
     object requires MAKE_REG in the directory which the whiteout moves into
   - Add audit test make_whiteout: denied whiteout creation is logged with
     blockers=fs.make_reg instead of fs.make_char
 - Tests, addressing the v4 feedback on patch 4/5:
   - Rework the OverlayFS rename test to rename a FIFO which originates
     from the lower layer, instead of preparing it in the upper layer with
     unlink() and mknod().  The unlink() had already created the whiteout
     in the upper layer before Landlock was enforced
   - Check that the whiteout object in the upper layer did not exist before
     the rename
 - Verified that the new tests fail on a kernel which lacks the fix from
   patch 2/5

v5:
 - Erratum: Clarify that this is about mknod(2) only
 - Add Cc stable@vger.kernel.org and Depends-on lines
 - Smaller code changes
   - Use dev_t where appropriate
   - Introduce get_dentry_access() helper
   - Use get_mode_access(S_IFCHR | WHITEOUT_MODE, WHITEOUT_DEV)
     to calculate the required access right for the created whiteout object
 - Tests:
   - rename_whiteout_denied: move a FIFO
     - otherwise, the test succeeded without the change
   - make_whiteout: drop set_cap(CAP_MKNOD) (not needed)
 - This is an intermediary state: It incorporates feedback to 2/5 and some 3/5
   feedback from v4, but not the 4/5 feedback yet.
 - https://lore.kernel.org/all/20260731154353.1790268-1-gnoack@google.com/

v4:
 - Guard it with LANDLOCK_ACCESS_FS_MAKE_REG as discussed.
 - Selftests and documentation.
 - https://lore.kernel.org/all/20260724161004.2360749-1-gnoack@google.com/

v3:
 - Do LANDLOCK_ACCESS_FS_MAKE_WHITEOUT check as part of
   current_check_refer_path().
 - https://lore.kernel.org/all/20260610092318.3868884-1-gnoack@google.com/

v2:
 - Introduce LANDLOCK_ACCESS_FS_MAKE_WHITEOUT access right
   and guard it with that.
 - Bump ABI version
 - https://lore.kernel.org/all/20260513160552.4022649-1-gnoack@google.com/

v1:
 - initial version
   https://lore.kernel.org/all/20260411090944.3131168-2-gnoack@google.com/


Günther Noack (6):
  selftests/landlock: Use an actual chardev for MAKE_CHAR audit test
  landlock: Require LANDLOCK_ACCESS_FS_MAKE_REG for whiteout creation
  selftests/landlock: Add tests for whiteout object creation
  selftests/landlock: Add audit test for whiteout object creation
  selftests/landlock: Test whiteout object behaviour in OverlayFS
    renames
  landlock: Link the erratum documentation for whiteout objects

 Documentation/userspace-api/landlock.rst   |   3 +
 include/uapi/linux/landlock.h              |   1 +
 security/landlock/errata/abi-1.h           |  23 ++
 security/landlock/fs.c                     |  41 +++-
 tools/testing/selftests/landlock/fs_test.c | 253 ++++++++++++++++++++-
 5 files changed, 309 insertions(+), 12 deletions(-)

Range-diff against v5:
1:  d1a8a84d92e0 = 1:  49a6fa0831e8 selftests/landlock: Use an actual chardev for MAKE_CHAR audit test
2:  9fe2edf17413 ! 2:  a29c7919a412 landlock: Require LANDLOCK_ACCESS_FS_MAKE_REG for whiteout creation
    @@ Commit message
         For the mknod(2) case, introduce a Landlock erratum.  The creation of
         whiteout objects through mknod(2) was previously guarded using
         LANDLOCK_ACCESS_FS_MAKE_CHAR, and it is now guarded using
    -    LANDLOCK_ACCESS_MAKE_REG.
    +    LANDLOCK_ACCESS_FS_MAKE_REG.
     
         For the renameat2(2) case, fix a bug: Before this commit, renameat2(2)
         with RENAME_WHITEOUT would create a directory entry even when all
    @@ security/landlock/fs.c: static __attribute_const__ access_mask_t get_mode_access
      	}
      }
      
    -+static __attribute_const__ access_mask_t
    -+get_dentry_access(const struct dentry *const dentry)
    ++static access_mask_t get_dentry_access(const struct dentry *const dentry)
     +{
    -+	return get_mode_access(d_backing_inode(dentry)->i_mode,
    -+			       d_backing_inode(dentry)->i_rdev);
    ++	const struct inode *const inode = d_backing_inode(dentry);
    ++
    ++	return get_mode_access(inode->i_mode, inode->i_rdev);
     +}
     +
      static access_mask_t maybe_remove(const struct dentry *const dentry)
3:  d23ea62b3325 ! 3:  0711d9e709fb selftests/landlock: Add tests for whiteout object creation
    @@ tools/testing/selftests/landlock/fs_test.c: TEST_F_FORK(layout1, rename_file)
     +				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)
      {
    @@ tools/testing/selftests/landlock/fs_test.c: TEST_F_FORK(layout1, make_char)
      
     +TEST_F_FORK(layout1, make_whiteout)
     +{
    -+	/* Creates a whiteout object (creation guarded by MAKE_REG). */
    ++	/*
    ++	 * 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));
     +}
-:  ------------ > 4:  f61b6ddb635e selftests/landlock: Add audit test for whiteout object creation
4:  10ed1d74636d ! 5:  4862aead03f2 selftests/landlock: Test whiteout object behaviour in OverlayFS renames
    @@ Metadata
      ## Commit message ##
         selftests/landlock: Test whiteout object behaviour in OverlayFS renames
     
    -    Even though OverlayFS uses vfs_rename() with RENAME_WHITEOUT, and even
    -    though RENAME_WHITEOUT requires LANDLOCK_ACCESS_FS_MAKE_REG, a process
    -    that renames non-regular files in an OverlayFS can do so without
    -    having the LANDLOCK_ACCESS_FS_MAKE_REG right in that location.
    +    Even though OverlayFS uses vfs_rename() with RENAME_WHITEOUT on its backing
    +    directories, and even though RENAME_WHITEOUT requires
    +    LANDLOCK_ACCESS_FS_MAKE_REG, a process that renames non-regular files in an
    +    OverlayFS can do so without having the LANDLOCK_ACCESS_FS_MAKE_REG right in
    +    that location.
     
    -    This works, and is supposed to work, because OverlayFS uses the
    -    credentials determined at mount time for the internal vfs_rename()
    -    operation.  The rename happens with the credentials of the user who
    -    mounted the OverlayFS.
    +    This works, and is supposed to work, because the changes to the backing
    +    directories are done by OverlayFS, not by the originator task that did the
    +    original rename() on the OverlayFS mount.  Therefore, the changes done to
    +    backing directories are not subject to the originator task's credentials.
     
         Signed-off-by: Günther Noack <gnoack@google.com>
     
      ## tools/testing/selftests/landlock/fs_test.c ##
    +@@ tools/testing/selftests/landlock/fs_test.c: static bool is_fifo(const char *const path)
    + 	return stat(path, &st) == 0 && S_ISFIFO(st.st_mode);
    + }
    + 
    ++static bool is_missing(const char *const path)
    ++{
    ++	struct stat st;
    ++
    ++	return stat(path, &st) == -1 && errno == ENOENT;
    ++}
    ++
    + TEST_F_FORK(layout1, rename_whiteout_allowed)
    + {
    + 	const struct rule rules[] = {
    +@@ tools/testing/selftests/landlock/fs_test.c: static const char lower_fo1[] = LOWER_DATA "/fo1";
    + static const char lower_do1[] = LOWER_DATA "/do1";
    + static const char lower_do1_fo2[] = LOWER_DATA "/do1/fo2";
    + static const char lower_do1_fl3[] = LOWER_DATA "/do1/fl3";
    ++/* lower_pl1 is a FIFO and is deliberately not in the lists below. */
    ++static const char lower_pl1[] = LOWER_DATA "/pl1";
    + 
    + static const char (*lower_base_files[])[] = {
    + 	&lower_fl1,
    +@@ tools/testing/selftests/landlock/fs_test.c: static const char (*upper_sub_files[])[] = {
    + #define MERGE_BASE TMP_DIR "/merge"
    + #define MERGE_DATA MERGE_BASE "/data"
    + static const char merge_fl1[] = MERGE_DATA "/fl1";
    ++/* merge_pl1 is a FIFO and is deliberately not in the lists below. */
    ++static const char merge_pl1[] = MERGE_DATA "/pl1";
    + static const char merge_dl1[] = MERGE_DATA "/dl1";
    + static const char merge_dl1_fl2[] = MERGE_DATA "/dl1/fl2";
    + static const char merge_fu1[] = MERGE_DATA "/fu1";
    +@@ tools/testing/selftests/landlock/fs_test.c: static const char (*merge_sub_files[])[] = {
    +  * │       │   ├── fl3
    +  * │       │   └── fo2
    +  * │       ├── fl1
    +- * │       └── fo1
    ++ * │       ├── fo1
    ++ * │       └── pl1 [FIFO]
    +  * ├── merge
    +  * │   └── data
    +  * │       ├── dl1
    +@@ tools/testing/selftests/landlock/fs_test.c: static const char (*merge_sub_files[])[] = {
    +  * │       │   └── fu2
    +  * │       ├── fl1
    +  * │       ├── fo1
    +- * │       └── fu1
    ++ * │       ├── fu1
    ++ * │       └── pl1 [FIFO]
    +  * └── upper
    +  *     ├── data
    +  *     │   ├── do1
    +@@ tools/testing/selftests/landlock/fs_test.c: FIXTURE_SETUP(layout2_overlay)
    + 	create_file(_metadata, lower_fo1);
    + 	create_file(_metadata, lower_do1_fo2);
    + 	create_file(_metadata, lower_do1_fl3);
    ++	ASSERT_EQ(0, mknod(lower_pl1, S_IFIFO | 0600, 0));
    + 
    + 	create_directory(_metadata, UPPER_BASE);
    + 	set_cap(_metadata, CAP_SYS_ADMIN);
    +@@ tools/testing/selftests/landlock/fs_test.c: FIXTURE_TEARDOWN_PARENT(layout2_overlay)
    + 	EXPECT_EQ(0, remove_path(lower_fl1));
    + 	EXPECT_EQ(0, remove_path(lower_do1_fo2));
    + 	EXPECT_EQ(0, remove_path(lower_fo1));
    ++	EXPECT_EQ(0, remove_path(lower_pl1));
    + 
    + 	/* umount(LOWER_BASE)) is handled by namespace lifetime. */
    + 	EXPECT_EQ(0, remove_path(LOWER_BASE));
     @@ tools/testing/selftests/landlock/fs_test.c: TEST_F_FORK(layout2_overlay, same_content_different_file)
      	}
      }
      
     +TEST_F_FORK(layout2_overlay, rename_in_overlay_without_make_reg)
     +{
    -+	struct stat st;
    -+	const char *merge_fl1_renamed = MERGE_DATA "/fl1_renamed";
    ++	const char *const merge_pl1_renamed = MERGE_DATA "/pl1_renamed";
     +
     +	if (self->skip_test)
     +		SKIP(return, "overlayfs is not supported (test)");
     +
     +	/*
    -+	 * In this test, merge_fl1 is a FIFO file.  MAKE_REG is restricted, but
    -+	 * MAKE_FIFO is allowed.  Despite MAKE_REG being restricted, the rename
    -+	 * on the OverlayFS works and creates a whiteout file in the underlying
    -+	 * upper file system.
    ++	 * merge_pl1 is a FIFO which only exists in the lower layer.  Before
    ++	 * the rename, the upper layer has no entry under this name.
     +	 */
    -+	ASSERT_EQ(0, unlink(merge_fl1));
    -+	ASSERT_EQ(0, mknod(merge_fl1, S_IFIFO, 0));
    ++	ASSERT_TRUE(is_fifo(merge_pl1));
    ++	ASSERT_TRUE(is_missing(UPPER_DATA "/pl1"));
    ++
    ++	/* MAKE_REG is restricted, but MAKE_FIFO is not. */
     +	enforce_fs(_metadata, LANDLOCK_ACCESS_FS_MAKE_REG, NULL);
     +
     +	/*
    -+	 * Execute a regular file rename within OverlayFS.
    -+	 * merge_fl1 originates from lower layer, so this triggers a copy-up
    -+	 * and creation of a whiteout in the upper layer.
    ++	 * Rename the FIFO through OverlayFS.  merge_pl1 originates from the
    ++	 * lower layer, so this triggers a copy-up and creates the whiteout in
    ++	 * the upper layer to hide the lower layer FIFO file.  Even though
    ++	 * MAKE_REG is restricted, the rename on the OverlayFS works.
     +	 */
    -+	EXPECT_EQ(0, rename(merge_fl1, merge_fl1_renamed));
    ++	EXPECT_EQ(0, rename(merge_pl1, merge_pl1_renamed));
     +
     +	/* Check that the rename worked. */
    -+	EXPECT_EQ(0, stat(merge_fl1_renamed, &st));
    -+	EXPECT_EQ(-1, stat(merge_fl1, &st));
    -+	EXPECT_EQ(ENOENT, errno);
    ++	EXPECT_TRUE(is_fifo(merge_pl1_renamed));
    ++	EXPECT_TRUE(is_missing(merge_pl1));
     +
     +	/*
    -+	 * Check that the whiteout object on the underlying "upper" filesystem
    -+	 * exists after the rename.  This is OK because it was done with the
    -+	 * credentials of the OverlayFS.
    ++	 * Check that the whiteout object was created on the underlying "upper"
    ++	 * filesystem during the rename.  This is OK because the whiteout object
    ++	 * was created by OverlayFS, not by the calling task.
     +	 */
    -+	EXPECT_EQ(0, stat(UPPER_DATA "/fl1", &st));
    -+	EXPECT_TRUE(S_ISCHR(st.st_mode));
    -+	EXPECT_EQ(0, st.st_rdev);
    ++	EXPECT_TRUE(is_whiteout(UPPER_DATA "/pl1"));
     +}
     +
      FIXTURE(layout3_fs)
5:  35651219395c = 6:  4a53acb99752 landlock: Link the erratum documentation for whiteout objects
-- 
2.55.0.699.gb54405d56f-goog


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

* [PATCH v6 1/6] selftests/landlock: Use an actual chardev for MAKE_CHAR audit test
  2026-08-13  9:31 [PATCH v6 0/6] landlock: Restrict whiteout object creation Günther Noack
@ 2026-08-13  9:31 ` 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
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 8+ messages in thread
From: Günther Noack @ 2026-08-13  9:31 UTC (permalink / raw)
  To: Mickaël Salaün, Christian Brauner
  Cc: linux-security-module, Paul Moore, Amir Goldstein, Miklos Szeredi,
	Serge Hallyn, Stephen Smalley, Günther Noack

By passing a (0, 0) device number, the audit test for
LANDLOCK_ACCESS_FS_MAKE_CHAR was accidentally creating a whiteout object
rather than a char device.  In preparation to treating whiteout objects
differently, use an actual character device instead.

Signed-off-by: Günther Noack <gnoack@google.com>
---
 tools/testing/selftests/landlock/fs_test.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index 86e08aa6e0a7..e82b56a74c5f 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -7436,7 +7436,7 @@ TEST_F(audit_layout1, make_char)
 
 	enforce_fs(_metadata, ACCESS_ALL, NULL);
 
-	EXPECT_EQ(-1, mknod(file1_s1d3, S_IFCHR | 0644, 0));
+	EXPECT_EQ(-1, mknod(file1_s1d3, S_IFCHR | 0644, makedev(7, 0)));
 	EXPECT_EQ(EACCES, errno);
 	EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.make_char",
 				    dir_s1d3));
-- 
2.55.0.699.gb54405d56f-goog


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

* [PATCH v6 2/6] landlock: Require LANDLOCK_ACCESS_FS_MAKE_REG for whiteout creation
  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 ` Günther Noack
  2026-08-13  9:31 ` [PATCH v6 3/6] selftests/landlock: Add tests for whiteout object creation Günther Noack
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 8+ messages in thread
From: Günther Noack @ 2026-08-13  9:31 UTC (permalink / raw)
  To: Mickaël Salaün, Christian Brauner
  Cc: linux-security-module, Paul Moore, Amir Goldstein, Miklos Szeredi,
	Serge Hallyn, Stephen Smalley, Günther Noack, stable

Whiteout objects are used in the upper layer of an OverlayFS to
indicate that the file with this name does not exist in the unified
view, even if it is present in one of the lower layer file systems.

For the userspace implementations of OverlayFS (fuse-overlayfs),
whiteout objects can be created from userspace as well:

* mknod(2) with S_IFCHR and makedev(0, 0)
* renameat2(2) with RENAME_WHITEOUT,
  creating the whiteout in the old place of the moved file.

This commit guards whiteout creation in both of these cases with
LANDLOCK_ACCESS_FS_MAKE_REG.  Whiteout objects are *not* considered
character devices and are not bound to a driver.

For the mknod(2) case, introduce a Landlock erratum.  The creation of
whiteout objects through mknod(2) was previously guarded using
LANDLOCK_ACCESS_FS_MAKE_CHAR, and it is now guarded using
LANDLOCK_ACCESS_FS_MAKE_REG.

For the renameat2(2) case, fix a bug: Before this commit, renameat2(2)
with RENAME_WHITEOUT would create a directory entry even when all
LANDLOCK_ACCESS_FS_MAKE_* rights were denied.

This does not affect normal renames within layered OverlayFS mounts:
When doing a regular rename() on a mounted fuse-overlayfs, it is the
fuse-overlayfs daemon that exercises renameat2() with RENAME_WHITEOUT,
and only the Landlock domain of that daemon is checked there.

Suggested-by: Christian Brauner <brauner@kernel.org>
Suggested-by: Mickaël Salaün <mic@digikod.net>
Cc: stable@vger.kernel.org
Fixes: cb2c7d1a1776 ("landlock: Support filesystem access-control")
Depends-on: 49c9e09d9610 ("landlock: Fix handling of disconnected directories")
Depends-on: fe72ce6710cb ("landlock: Add errata documentation section")
Signed-off-by: Günther Noack <gnoack@google.com>
---
 include/uapi/linux/landlock.h    |  1 +
 security/landlock/errata/abi-1.h | 23 ++++++++++++++++++
 security/landlock/fs.c           | 41 +++++++++++++++++++++++++-------
 3 files changed, 56 insertions(+), 9 deletions(-)

diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index 7ffe2ef127ee..9c1102ebf06e 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -351,6 +351,7 @@ struct landlock_net_port_attr {
  *   device.
  * - %LANDLOCK_ACCESS_FS_MAKE_DIR: Create (or rename) a directory.
  * - %LANDLOCK_ACCESS_FS_MAKE_REG: Create (or rename or link) a regular file.
+ *   This also guards the creation of whiteout objects as used in OverlayFS.
  * - %LANDLOCK_ACCESS_FS_MAKE_SOCK: Create (or rename or link) a UNIX domain
  *   socket.
  * - %LANDLOCK_ACCESS_FS_MAKE_FIFO: Create (or rename or link) a named pipe.
diff --git a/security/landlock/errata/abi-1.h b/security/landlock/errata/abi-1.h
index 3f099555f059..e0d543d9d508 100644
--- a/security/landlock/errata/abi-1.h
+++ b/security/landlock/errata/abi-1.h
@@ -22,3 +22,26 @@
  * from their original mount points.
  */
 LANDLOCK_ERRATUM(3)
+
+/**
+ * DOC: erratum_4
+ *
+ * Erratum 4: Creation of whiteout objects
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This fix changes the access rights required for the creation of whiteout
+ * objects through :manpage:`mknod(2)` or :manpage:`renameat2(2)`.  Creating
+ * whiteout objects is now guarded by ``LANDLOCK_ACCESS_FS_MAKE_REG`` instead of
+ * ``LANDLOCK_ACCESS_FS_MAKE_CHAR``.
+ *
+ * Whiteout objects are used in OverlayFS to mark the absence of a file in an
+ * upper file system.  Despite being created with ``S_IFCHR``, whiteout objects
+ * do not count as character devices.
+ *
+ * Impact:
+ *
+ * Sandboxed programs that create OverlayFS whiteouts (such as fuse-overlayfs)
+ * now require ``LANDLOCK_ACCESS_FS_MAKE_REG`` instead of
+ * ``LANDLOCK_ACCESS_FS_MAKE_CHAR``.
+ */
+LANDLOCK_ERRATUM(4)
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index f7e5e4ef9eac..8fc7f82a374a 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -20,6 +20,7 @@
 #include <linux/falloc.h>
 #include <linux/fs.h>
 #include <linux/init.h>
+#include <linux/kdev_t.h>
 #include <linux/kernel.h>
 #include <linux/limits.h>
 #include <linux/list.h>
@@ -983,7 +984,8 @@ static int current_check_access_path(const struct path *const path,
 	return -EACCES;
 }
 
-static __attribute_const__ access_mask_t get_mode_access(const umode_t mode)
+static __attribute_const__ access_mask_t get_mode_access(const umode_t mode,
+							 const dev_t dev)
 {
 	switch (mode & S_IFMT) {
 	case S_IFLNK:
@@ -991,6 +993,9 @@ static __attribute_const__ access_mask_t get_mode_access(const umode_t mode)
 	case S_IFDIR:
 		return LANDLOCK_ACCESS_FS_MAKE_DIR;
 	case S_IFCHR:
+		/* Whiteout objects are guarded with MAKE_REG. */
+		if (dev == WHITEOUT_DEV)
+			return LANDLOCK_ACCESS_FS_MAKE_REG;
 		return LANDLOCK_ACCESS_FS_MAKE_CHAR;
 	case S_IFBLK:
 		return LANDLOCK_ACCESS_FS_MAKE_BLOCK;
@@ -1007,6 +1012,13 @@ static __attribute_const__ access_mask_t get_mode_access(const umode_t mode)
 	}
 }
 
+static access_mask_t get_dentry_access(const struct dentry *const dentry)
+{
+	const struct inode *const inode = d_backing_inode(dentry);
+
+	return get_mode_access(inode->i_mode, inode->i_rdev);
+}
+
 static access_mask_t maybe_remove(const struct dentry *const dentry)
 {
 	if (d_is_negative(dentry))
@@ -1093,6 +1105,7 @@ static bool collect_domain_accesses(const struct landlock_ruleset *const domain,
  * @new_dentry: Destination file or directory.
  * @removable: Sets to true if it is a rename operation.
  * @exchange: Sets to true if it is a rename operation with RENAME_EXCHANGE.
+ * @whiteout: Sets to true if it is a rename operation with RENAME_WHITEOUT.
  *
  * Because of its unprivileged constraints, Landlock relies on file hierarchies
  * (and not only inodes) to tie access rights to files.  Being able to link or
@@ -1140,7 +1153,8 @@ static bool collect_domain_accesses(const struct landlock_ruleset *const domain,
 static int current_check_refer_path(struct dentry *const old_dentry,
 				    const struct path *const new_dir,
 				    struct dentry *const new_dentry,
-				    const bool removable, const bool exchange)
+				    const bool removable, const bool exchange,
+				    const bool whiteout)
 {
 	const struct landlock_cred_security *const subject =
 		landlock_get_applicable_subject(current_cred(), any_fs, NULL);
@@ -1159,18 +1173,25 @@ static int current_check_refer_path(struct dentry *const old_dentry,
 	if (exchange) {
 		if (unlikely(d_is_negative(new_dentry)))
 			return -ENOENT;
-		access_request_parent1 =
-			get_mode_access(d_backing_inode(new_dentry)->i_mode);
+		access_request_parent1 = get_dentry_access(new_dentry);
 	} else {
 		access_request_parent1 = 0;
 	}
-	access_request_parent2 =
-		get_mode_access(d_backing_inode(old_dentry)->i_mode);
+	access_request_parent2 = get_dentry_access(old_dentry);
 	if (removable) {
 		access_request_parent1 |= maybe_remove(old_dentry);
 		access_request_parent2 |= maybe_remove(new_dentry);
 	}
 
+	/*
+	 * In case of renameat2(2) with RENAME_WHITEOUT, a whiteout object is
+	 * created in the source location, so we require an additional access
+	 * right there.
+	 */
+	if (whiteout)
+		access_request_parent1 |=
+			get_mode_access(S_IFCHR | WHITEOUT_MODE, WHITEOUT_DEV);
+
 	/* The mount points are the same for old and new paths, cf. EXDEV. */
 	if (old_dentry->d_parent == new_dir->dentry) {
 		/*
@@ -1520,7 +1541,7 @@ static int hook_path_link(struct dentry *const old_dentry,
 			  struct dentry *const new_dentry)
 {
 	return current_check_refer_path(old_dentry, new_dir, new_dentry, false,
-					false);
+					false, false);
 }
 
 static int hook_path_rename(const struct path *const old_dir,
@@ -1531,7 +1552,8 @@ static int hook_path_rename(const struct path *const old_dir,
 {
 	/* old_dir refers to old_dentry->d_parent and new_dir->mnt */
 	return current_check_refer_path(old_dentry, new_dir, new_dentry, true,
-					!!(flags & RENAME_EXCHANGE));
+					!!(flags & RENAME_EXCHANGE),
+					!!(flags & RENAME_WHITEOUT));
 }
 
 static int hook_path_mkdir(const struct path *const dir,
@@ -1544,7 +1566,8 @@ static int hook_path_mknod(const struct path *const dir,
 			   struct dentry *const dentry, const umode_t mode,
 			   const unsigned int dev)
 {
-	return current_check_access_path(dir, get_mode_access(mode));
+	return current_check_access_path(
+		dir, get_mode_access(mode, new_decode_dev(dev)));
 }
 
 static int hook_path_symlink(const struct path *const dir,
-- 
2.55.0.699.gb54405d56f-goog


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

* [PATCH v6 3/6] selftests/landlock: Add tests for whiteout object creation
  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
  2026-08-13 11:54   ` Günther Noack
  2026-08-13  9:31 ` [PATCH v6 4/6] selftests/landlock: Add audit test " Günther Noack
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 8+ messages in thread
From: Günther Noack @ 2026-08-13  9:31 UTC (permalink / raw)
  To: Mickaël Salaün, Christian Brauner
  Cc: linux-security-module, Paul Moore, Amir Goldstein, Miklos Szeredi,
	Serge Hallyn, Stephen Smalley, Günther Noack

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


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

* [PATCH v6 4/6] selftests/landlock: Add audit test for whiteout object creation
  2026-08-13  9:31 [PATCH v6 0/6] landlock: Restrict whiteout object creation Günther Noack
                   ` (2 preceding siblings ...)
  2026-08-13  9:31 ` [PATCH v6 3/6] selftests/landlock: Add tests for whiteout object creation Günther Noack
@ 2026-08-13  9:31 ` 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
  5 siblings, 0 replies; 8+ messages in thread
From: Günther Noack @ 2026-08-13  9:31 UTC (permalink / raw)
  To: Mickaël Salaün, Christian Brauner
  Cc: linux-security-module, Paul Moore, Amir Goldstein, Miklos Szeredi,
	Serge Hallyn, Stephen Smalley, Günther Noack

Add audit_layout1.make_whiteout: This test looks similar to
audit_layout1.make_char, but creates a whiteout object through mknod().
Since whiteout object creation is now guarded with
LANDLOCK_ACCESS_FS_MAKE_REG rather than LANDLOCK_ACCESS_FS_MAKE_CHAR, it
also needs to log the matching denial to audit.

Signed-off-by: Günther Noack <gnoack@google.com>
---
 tools/testing/selftests/landlock/fs_test.c | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index 871a5b819b98..a9130ed70af5 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -7622,6 +7622,25 @@ TEST_F(audit_layout1, make_char)
 	EXPECT_EQ(1, records.domain);
 }
 
+TEST_F(audit_layout1, make_whiteout)
+{
+	struct audit_records records;
+
+	EXPECT_EQ(0, unlink(file1_s1d3));
+
+	enforce_fs(_metadata, ACCESS_ALL, NULL);
+
+	/* Whiteout creation is denied and logged as fs.make_reg. */
+	EXPECT_EQ(-1, mknod(file1_s1d3, S_IFCHR | 0644, makedev(0, 0)));
+	EXPECT_EQ(EACCES, errno);
+	EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.make_reg",
+				    dir_s1d3));
+
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	EXPECT_EQ(0, records.access);
+	EXPECT_EQ(1, records.domain);
+}
+
 TEST_F(audit_layout1, make_dir)
 {
 	struct audit_records records;
-- 
2.55.0.699.gb54405d56f-goog


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

* [PATCH v6 5/6] selftests/landlock: Test whiteout object behaviour in OverlayFS renames
  2026-08-13  9:31 [PATCH v6 0/6] landlock: Restrict whiteout object creation Günther Noack
                   ` (3 preceding siblings ...)
  2026-08-13  9:31 ` [PATCH v6 4/6] selftests/landlock: Add audit test " Günther Noack
@ 2026-08-13  9:31 ` Günther Noack
  2026-08-13  9:31 ` [PATCH v6 6/6] landlock: Link the erratum documentation for whiteout objects Günther Noack
  5 siblings, 0 replies; 8+ messages in thread
From: Günther Noack @ 2026-08-13  9:31 UTC (permalink / raw)
  To: Mickaël Salaün, Christian Brauner
  Cc: linux-security-module, Paul Moore, Amir Goldstein, Miklos Szeredi,
	Serge Hallyn, Stephen Smalley, Günther Noack

Even though OverlayFS uses vfs_rename() with RENAME_WHITEOUT on its backing
directories, and even though RENAME_WHITEOUT requires
LANDLOCK_ACCESS_FS_MAKE_REG, a process that renames non-regular files in an
OverlayFS can do so without having the LANDLOCK_ACCESS_FS_MAKE_REG right in
that location.

This works, and is supposed to work, because the changes to the backing
directories are done by OverlayFS, not by the originator task that did the
original rename() on the OverlayFS mount.  Therefore, the changes done to
backing directories are not subject to the originator task's credentials.

Signed-off-by: Günther Noack <gnoack@google.com>
---
 tools/testing/selftests/landlock/fs_test.c | 56 +++++++++++++++++++++-
 1 file changed, 54 insertions(+), 2 deletions(-)

diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index a9130ed70af5..0531701e8ce6 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -2283,6 +2283,13 @@ static bool is_fifo(const char *const path)
 	return stat(path, &st) == 0 && S_ISFIFO(st.st_mode);
 }
 
+static bool is_missing(const char *const path)
+{
+	struct stat st;
+
+	return stat(path, &st) == -1 && errno == ENOENT;
+}
+
 TEST_F_FORK(layout1, rename_whiteout_allowed)
 {
 	const struct rule rules[] = {
@@ -6635,6 +6642,8 @@ static const char lower_fo1[] = LOWER_DATA "/fo1";
 static const char lower_do1[] = LOWER_DATA "/do1";
 static const char lower_do1_fo2[] = LOWER_DATA "/do1/fo2";
 static const char lower_do1_fl3[] = LOWER_DATA "/do1/fl3";
+/* lower_pl1 is a FIFO and is deliberately not in the lists below. */
+static const char lower_pl1[] = LOWER_DATA "/pl1";
 
 static const char (*lower_base_files[])[] = {
 	&lower_fl1,
@@ -6684,6 +6693,8 @@ static const char (*upper_sub_files[])[] = {
 #define MERGE_BASE TMP_DIR "/merge"
 #define MERGE_DATA MERGE_BASE "/data"
 static const char merge_fl1[] = MERGE_DATA "/fl1";
+/* merge_pl1 is a FIFO and is deliberately not in the lists below. */
+static const char merge_pl1[] = MERGE_DATA "/pl1";
 static const char merge_dl1[] = MERGE_DATA "/dl1";
 static const char merge_dl1_fl2[] = MERGE_DATA "/dl1/fl2";
 static const char merge_fu1[] = MERGE_DATA "/fu1";
@@ -6724,7 +6735,8 @@ static const char (*merge_sub_files[])[] = {
  * │       │   ├── fl3
  * │       │   └── fo2
  * │       ├── fl1
- * │       └── fo1
+ * │       ├── fo1
+ * │       └── pl1 [FIFO]
  * ├── merge
  * │   └── data
  * │       ├── dl1
@@ -6737,7 +6749,8 @@ static const char (*merge_sub_files[])[] = {
  * │       │   └── fu2
  * │       ├── fl1
  * │       ├── fo1
- * │       └── fu1
+ * │       ├── fu1
+ * │       └── pl1 [FIFO]
  * └── upper
  *     ├── data
  *     │   ├── do1
@@ -6775,6 +6788,7 @@ FIXTURE_SETUP(layout2_overlay)
 	create_file(_metadata, lower_fo1);
 	create_file(_metadata, lower_do1_fo2);
 	create_file(_metadata, lower_do1_fl3);
+	ASSERT_EQ(0, mknod(lower_pl1, S_IFIFO | 0600, 0));
 
 	create_directory(_metadata, UPPER_BASE);
 	set_cap(_metadata, CAP_SYS_ADMIN);
@@ -6807,6 +6821,7 @@ FIXTURE_TEARDOWN_PARENT(layout2_overlay)
 	EXPECT_EQ(0, remove_path(lower_fl1));
 	EXPECT_EQ(0, remove_path(lower_do1_fo2));
 	EXPECT_EQ(0, remove_path(lower_fo1));
+	EXPECT_EQ(0, remove_path(lower_pl1));
 
 	/* umount(LOWER_BASE)) is handled by namespace lifetime. */
 	EXPECT_EQ(0, remove_path(LOWER_BASE));
@@ -7127,6 +7142,43 @@ TEST_F_FORK(layout2_overlay, same_content_different_file)
 	}
 }
 
+TEST_F_FORK(layout2_overlay, rename_in_overlay_without_make_reg)
+{
+	const char *const merge_pl1_renamed = MERGE_DATA "/pl1_renamed";
+
+	if (self->skip_test)
+		SKIP(return, "overlayfs is not supported (test)");
+
+	/*
+	 * merge_pl1 is a FIFO which only exists in the lower layer.  Before
+	 * the rename, the upper layer has no entry under this name.
+	 */
+	ASSERT_TRUE(is_fifo(merge_pl1));
+	ASSERT_TRUE(is_missing(UPPER_DATA "/pl1"));
+
+	/* MAKE_REG is restricted, but MAKE_FIFO is not. */
+	enforce_fs(_metadata, LANDLOCK_ACCESS_FS_MAKE_REG, NULL);
+
+	/*
+	 * Rename the FIFO through OverlayFS.  merge_pl1 originates from the
+	 * lower layer, so this triggers a copy-up and creates the whiteout in
+	 * the upper layer to hide the lower layer FIFO file.  Even though
+	 * MAKE_REG is restricted, the rename on the OverlayFS works.
+	 */
+	EXPECT_EQ(0, rename(merge_pl1, merge_pl1_renamed));
+
+	/* Check that the rename worked. */
+	EXPECT_TRUE(is_fifo(merge_pl1_renamed));
+	EXPECT_TRUE(is_missing(merge_pl1));
+
+	/*
+	 * Check that the whiteout object was created on the underlying "upper"
+	 * filesystem during the rename.  This is OK because the whiteout object
+	 * was created by OverlayFS, not by the calling task.
+	 */
+	EXPECT_TRUE(is_whiteout(UPPER_DATA "/pl1"));
+}
+
 FIXTURE(layout3_fs)
 {
 	bool has_created_dir;
-- 
2.55.0.699.gb54405d56f-goog


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

* [PATCH v6 6/6] landlock: Link the erratum documentation for whiteout objects
  2026-08-13  9:31 [PATCH v6 0/6] landlock: Restrict whiteout object creation Günther Noack
                   ` (4 preceding siblings ...)
  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 ` Günther Noack
  5 siblings, 0 replies; 8+ messages in thread
From: Günther Noack @ 2026-08-13  9:31 UTC (permalink / raw)
  To: Mickaël Salaün, Christian Brauner
  Cc: linux-security-module, Paul Moore, Amir Goldstein, Miklos Szeredi,
	Serge Hallyn, Stephen Smalley, Günther Noack

The documentation embeds the canonical erratum documentation from the
header file, which is already a self-contained description of the issue.

Signed-off-by: Günther Noack <gnoack@google.com>
---
 Documentation/userspace-api/landlock.rst | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
index 5a63d4476c1c..df996ccaee34 100644
--- a/Documentation/userspace-api/landlock.rst
+++ b/Documentation/userspace-api/landlock.rst
@@ -556,6 +556,9 @@ in the running kernel.
 .. kernel-doc:: security/landlock/errata/abi-1.h
     :doc: erratum_3
 
+.. kernel-doc:: security/landlock/errata/abi-1.h
+    :doc: erratum_4
+
 How to check for errata
 ~~~~~~~~~~~~~~~~~~~~~~~
 
-- 
2.55.0.699.gb54405d56f-goog


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

* Re: [PATCH v6 3/6] selftests/landlock: Add tests for whiteout object creation
  2026-08-13  9:31 ` [PATCH v6 3/6] selftests/landlock: Add tests for whiteout object creation Günther Noack
@ 2026-08-13 11:54   ` Günther Noack
  0 siblings, 0 replies; 8+ messages in thread
From: Günther Noack @ 2026-08-13 11:54 UTC (permalink / raw)
  To: Mickaël Salaün, Christian Brauner
  Cc: linux-security-module, Paul Moore, Amir Goldstein, Miklos Szeredi,
	Serge Hallyn, Stephen Smalley

On Thu, Aug 13, 2026 at 11:31:54AM +0200, Günther Noack wrote:
> 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

I forgot to update this commit message; this should say:

```
Add tests 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
* renameat2() with RENAME_EXCHANGE,
  with one of the renamed objects being a whiteout object
```

Please feel free to update it when picking up the change.

—Günther

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

end of thread, other threads:[~2026-08-13 11:54 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 ` [PATCH v6 3/6] selftests/landlock: Add tests for whiteout object creation Günther Noack
2026-08-13 11:54   ` 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

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.