[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