[LTP] [PATCH v1] memfd_create01: Reject F_SEAL_EXEC over a writable mapping

Stephen Bertram sbertram@redhat.com
Thu Nov 5 22:55:02 CET 2026


From: Stephen Bertram <sbertram@redhat.com>

On an executable memfd, F_SEAL_EXEC implies the write and size seals.
Adding it while a writable shared mapping exists must fail with EBUSY.
With no mapping held, the implied seals must be set.

Assisted-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Stephen Bertram <sbertram@redhat.com>
---
 include/lapi/fcntl.h                          |   6 +
 .../syscalls/memfd_create/memfd_create01.c    | 107 +++++++++++++++++-
 2 files changed, 111 insertions(+), 2 deletions(-)

diff --git a/include/lapi/fcntl.h b/include/lapi/fcntl.h
index d70954ea7..d826acba5 100644
--- a/include/lapi/fcntl.h
+++ b/include/lapi/fcntl.h
@@ -65,6 +65,12 @@
 #ifndef F_SEAL_WRITE
 # define F_SEAL_WRITE    0x0008  /* prevent writes */
 #endif
+#ifndef F_SEAL_FUTURE_WRITE
+# define F_SEAL_FUTURE_WRITE 0x0010  /* prevent future writes while mapped */
+#endif
+#ifndef F_SEAL_EXEC
+# define F_SEAL_EXEC     0x0020  /* prevent chmod modifying exec bits */
+#endif
 
 #ifndef F_OWNER_PGRP
 # define F_OWNER_PGRP 2
diff --git a/testcases/kernel/syscalls/memfd_create/memfd_create01.c b/testcases/kernel/syscalls/memfd_create/memfd_create01.c
index 28ad04a86..64e9daa77 100644
--- a/testcases/kernel/syscalls/memfd_create/memfd_create01.c
+++ b/testcases/kernel/syscalls/memfd_create/memfd_create01.c
@@ -1,12 +1,16 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 /*
- * Copyright (c) Linux Test Project, 2017-2020
+ * Copyright (c) Linux Test Project, 2017-2026
  * Copyright (C) 2017  Red Hat, Inc.
  * Port to LTP <jracek@redhat.com>
  */
 
 /*\
  * Test based on :kselftest:`memfd/memfd_test.c`.
+ *
+ * On an executable memfd, F_SEAL_EXEC implies F_SEAL_WRITE,
+ * F_SEAL_FUTURE_WRITE, F_SEAL_SHRINK and F_SEAL_GROW. F_SEAL_EXEC is
+ * rejected with EBUSY while a writable shared mapping exists.
  */
 
 #define _GNU_SOURCE
@@ -15,6 +19,9 @@
 #include "tst_test.h"
 #include "memfd_create_common.h"
 
+#define SEAL_EXEC_IMPLIED (F_SEAL_EXEC | F_SEAL_SHRINK | F_SEAL_GROW | \
+			   F_SEAL_WRITE | F_SEAL_FUTURE_WRITE)
+
 /*
  * Do few basic sealing tests to see whether setting/retrieving seals works.
  */
@@ -68,6 +75,75 @@ static void test_seal_write(int fd)
 	CHECK_MFD_NON_GROWABLE_BY_WRITE(fd);
 }
 
+/*
+ * F_SEAL_EXEC is only meaningful once the kernel knows the flag. Probe a
+ * separate memfd so a missing flag does not seal the caller's fd.
+ * Returns 1 when F_ADD_SEALS accepts F_SEAL_EXEC, 0 when the kernel rejects
+ * the flag with EINVAL.
+ */
+static int seal_exec_supported(void)
+{
+	static int supported = -1;
+	int fd;
+
+	if (supported >= 0)
+		return supported;
+
+	fd = sys_memfd_create("ltp_memfd_seal_exec", MFD_ALLOW_SEALING);
+	if (fd < 0)
+		tst_brk(TBROK | TERRNO, "memfd_create() for F_SEAL_EXEC probe");
+
+	TEST(fcntl(fd, F_ADD_SEALS, F_SEAL_EXEC));
+	SAFE_CLOSE(fd);
+
+	if (TST_RET == 0)
+		supported = 1;
+	else if (TST_ERR == EINVAL)
+		supported = 0;
+	else
+		tst_brk(TBROK | TTERRNO, "fcntl(F_ADD_SEALS, F_SEAL_EXEC)");
+
+	return supported;
+}
+
+/* F_SEAL_EXEC implies F_SEAL_WRITE only when the file mode is executable. */
+static int memfd_is_executable(int fd)
+{
+	struct stat st;
+
+	SAFE_FSTAT(fd, &st);
+
+	return !!(st.st_mode & 0111);
+}
+
+/*
+ * Test SEAL_EXEC
+ * On an executable memfd, F_SEAL_EXEC implies the write and size seals.
+ */
+static void test_seal_exec(int fd)
+{
+	if (!memfd_is_executable(fd)) {
+		tst_res(TCONF,
+			"no execute bit, F_SEAL_EXEC does not imply F_SEAL_WRITE");
+		return;
+	}
+
+	if (!seal_exec_supported()) {
+		tst_res(TCONF, "F_SEAL_EXEC is not supported");
+		return;
+	}
+
+	CHECK_MFD_HAS_SEALS(fd, 0);
+	CHECK_MFD_ADD_SEALS(fd, F_SEAL_EXEC);
+	CHECK_MFD_HAS_SEALS(fd, SEAL_EXEC_IMPLIED);
+
+	CHECK_MFD_READABLE(fd);
+	CHECK_MFD_NON_WRITEABLE(fd);
+	CHECK_MFD_NON_SHRINKABLE(fd);
+	CHECK_MFD_NON_GROWABLE(fd);
+	CHECK_MFD_NON_GROWABLE_BY_WRITE(fd);
+}
+
 /*
  * Test SEAL_SHRINK
  * Test whether SEAL_SHRINK actually prevents shrinking
@@ -164,12 +240,34 @@ static void test_share_mmap(int fd)
 
 	CHECK_MFD_HAS_SEALS(fd, 0);
 
-	/* shared/writable ref prevents sealing WRITE, but allows others */
+	/*
+	 * A shared writable mapping blocks F_SEAL_WRITE. F_SEAL_EXEC is
+	 * blocked too when it would imply F_SEAL_WRITE. Other seals, such
+	 * as F_SEAL_SHRINK, are still allowed.
+	 */
 	p = SAFE_MMAP(NULL, MFD_DEF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,
 		fd, 0);
 
 	CHECK_MFD_FAIL_ADD_SEALS(fd, F_SEAL_WRITE);
 	CHECK_MFD_HAS_SEALS(fd, 0);
+
+	if (memfd_is_executable(fd) && seal_exec_supported()) {
+		TST_EXP_FAIL(fcntl(fd, F_ADD_SEALS, F_SEAL_EXEC), EBUSY,
+			"F_SEAL_EXEC while a writable mapping exists");
+		if (!TST_PASS) {
+			SAFE_MUNMAP(p, MFD_DEF_SIZE);
+			return;
+		}
+
+		CHECK_MFD_HAS_SEALS(fd, 0);
+
+		*(volatile char *)p = 'x';
+		if (*(volatile char *)p != 'x')
+			tst_res(TFAIL, "writable mapping did not retain the write");
+		else
+			tst_res(TPASS, "writable mapping remained writable");
+	}
+
 	CHECK_MFD_ADD_SEALS(fd, F_SEAL_SHRINK);
 	CHECK_MFD_HAS_SEALS(fd, F_SEAL_SHRINK);
 	SAFE_MUNMAP(p, MFD_DEF_SIZE);
@@ -221,6 +319,7 @@ static const struct tcase {
 	{0,                 &test_no_sealing_without_flag, "Disabled sealing"},
 
 	{MFD_ALLOW_SEALING, &test_seal_write, "Write seal"},
+	{MFD_ALLOW_SEALING, &test_seal_exec, "Exec seal implies write seals"},
 	{MFD_ALLOW_SEALING, &test_seal_shrink, "Shrink seal"},
 	{MFD_ALLOW_SEALING, &test_seal_grow, "Grow seal"},
 	{MFD_ALLOW_SEALING, &test_seal_resize, "Resize seal"},
@@ -264,4 +363,8 @@ static struct tst_test test = {
 	.test = verify_memfd_create,
 	.tcnt = ARRAY_SIZE(tcases),
 	.setup = setup,
+	.tags = (const struct tst_tag[]) {
+		{"linux-git", "3b041514cb6e"},
+		{}
+	},
 };
-- 
2.55.0



More information about the ltp mailing list