All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] mm/memfd_luo: carry the exec-bit state in the preserved record
@ 2026-10-14  0:35 Haolong Wang
  2026-10-14  0:49 ` sashiko-bot
  0 siblings, 1 reply; 3+ messages in thread
From: Haolong Wang @ 2026-10-14  0:35 UTC (permalink / raw)
  To: Pasha Tatashin, Mike Rapoport, Pratyush Yadav
  Cc: Andrew Morton, kexec, linux-mm, linux-kernel

F_SEAL_EXEC freezes the exec bits as they are at sealing time: a memfd
becomes either permanently executable or permanently un-executable.
The preserved state of a memfd (struct memfd_luo_ser) carries the
seals but not the inode mode, and the restore path creates the file
with memfd_alloc_file("", MFD_ALLOW_SEALING) -- the default
(executable) mode -- without consulting the vm.memfd_noexec policy
(that policy is applied on the memfd_create() syscall path only).

A memfd sealed as un-executable is therefore restored as mode 0777
with F_SEAL_EXEC re-applied on top of it: the guarantee inverts.
Because the restored file is executable, memfd_add_seals() also
implies F_SEAL_WRITE, F_SEAL_FUTURE_WRITE, F_SEAL_SHRINK and
F_SEAL_GROW (the W^X rule), so the restored memfd comes back with its
write seals locked and its exec bits permanently irrevocable
(shmem_setattr() rejects any exec-bit change).  This is a functional
regression that needs no attacker: the owner's own pwrite() to the
memfd succeeds before the update and fails with EPERM after it.  The
inversion is also silent: F_GET_SEALS keeps reporting the seal.

Carry the fact in the existing ser->flags field, which commit
8a552d68a86e ("mm: memfd_luo: preserve file seals") added for exactly
this kind of extension ("Unused flag bits must be set to 0"): preserve
records whether the inode had exec bits, and retrieve reproduces them
by setting the mode of the fresh anonymous inode directly, before the
seals are re-applied -- the W^X rule in memfd_add_seals() looks at the
mode when it decides whether to imply the write seals, so the mode has
to be in place first.  The file is not published at that point, so no
other thread can observe the intermediate state.  Reusing the
MFD_NOEXEC_SEAL alloc flag was considered and dropped: it would
silently add F_SEAL_EXEC to memfds that never had it (a plain
non-executable memfd without the seal would come back with the seal
attached and its exec bits irrevocable), and it would couple the
restore to the current policy rather than to the state that was
preserved.  A record that does not say the bits were set is restored
without them -- which is what a memfd created under vm.memfd_noexec=2
looks like, and is also the safe reading for records written before
this bit existed.

With this, the round trip is exact: a NX memfd (0666 + 0x20) is
restored as 0666 + 0x20 and stays writable by its owner; a plain
non-executable memfd (0666, unsealed) stays 0666 and unsealed, its
exec bits still chmod-able; the baseline executable memfd stays 0777
and still execve's; and memfd_create() under vm.memfd_noexec=2 still
forces NX on fresh memfds.  No seal is invented for any of them.

Refusing to preserve memfds with F_SEAL_EXEC was considered and
rejected: with vm.memfd_noexec >= 1 every memfd created without an
explicit MFD_EXEC gets MFD_NOEXEC_SEAL forced on it (and = 2 refuses
the MFD_EXEC opt-out outright), so the refusal would disable the whole
preservation feature exactly on the systems that harden the most.
Preserving the full inode mode would be the alternative, but one bit in
the already-extensible flags field is sufficient and keeps the record
layout unchanged.

One point for the maintainers: a clear exec bit in a record is
ambiguous between "written before this bit existed" and "the bits were
clear", and both readings are safe -- the memfd comes back
non-executable, which is what a memfd created under vm.memfd_noexec=2
looks like.  For an old record it does change one case: an executable
memfd with F_SEAL_EXEC preserved by a kernel without this change is
restored as permanently non-executable instead of permanently
executable.  If that is not acceptable, the reading has to be tied to a
version bump instead: commit 8a552d68a86e ("mm: memfd_luo: preserve
file seals") already noted that new semantics may require one ("they
will need another version bump").

Measured on a QEMU live-update run (preserve -> kexec -> retrieve):
before the fix the restored NX memfd is mode 0777, seals 0x3e, execve
of its contents succeeds and the owner cannot write to it; with the
fix the same test reports the round trip exact (mode 0666, seals 0x20,
still writable, execve -> EACCES), a plain non-executable memfd comes
back 0666 with no seal and can be chmod'ed executable again, and the
baseline executable memfd and fresh memfds are unaffected.

Fixes: 8a552d68a86e ("mm: memfd_luo: preserve file seals")
Assisted-by: Pi:glm-5.3-flash
Signed-off-by: Haolong Wang <18766350203@163.com>
---
 include/linux/kho/abi/memfd.h |  3 +++
 mm/memfd_luo.c                | 31 +++++++++++++++++++++++++++++--
 2 files changed, 32 insertions(+), 2 deletions(-)

diff --git a/include/linux/kho/abi/memfd.h b/include/linux/kho/abi/memfd.h
index 08b10fea2afc..471cfe8a4616 100644
--- a/include/linux/kho/abi/memfd.h
+++ b/include/linux/kho/abi/memfd.h
@@ -87,6 +87,9 @@ struct memfd_luo_ser {
 	struct kho_vmalloc folios;
 } __packed;
 
+/* The execute bits were set on the memfd when its seals were applied. */
+#define MEMFD_LUO_FL_EXEC_BIT		BIT(0)
+
 /* The compatibility string for memfd file handler */
 #define MEMFD_LUO_FH_COMPATIBLE	"memfd-v2"
 
diff --git a/mm/memfd_luo.c b/mm/memfd_luo.c
index 59de210bee5f..950a43a4c328 100644
--- a/mm/memfd_luo.c
+++ b/mm/memfd_luo.c
@@ -300,6 +300,17 @@ static int memfd_luo_preserve(struct liveupdate_file_op_args *args)
 	ser->size = inode_size;
 	ser->seals = seals;
 
+	/*
+	 * F_SEAL_EXEC freezes the exec bits as they are at sealing time, so
+	 * the restore has to reproduce them.  Record whether the inode had
+	 * exec bits here; the restore sets the mode of the fresh inode
+	 * accordingly before re-applying the seals.  ser is zero-initialized,
+	 * so a clear bit means the bits were clear (or the record predates
+	 * this flag -- the safe reading for those is also "no exec bits").
+	 */
+	if (inode->i_mode & 0111)
+		ser->flags |= MEMFD_LUO_FL_EXEC_BIT;
+
 	err = memfd_luo_preserve_folios(args->file, &ser->folios,
 					&folios_ser, &nr_folios);
 	if (err)
@@ -533,8 +544,11 @@ static int memfd_luo_retrieve(struct liveupdate_file_op_args *args)
 	}
 
 	/*
-	 * The seals are preserved. Allow sealing here so they can be added
-	 * later.
+	 * F_SEAL_EXEC freezes the execute bits as they were when the seal was
+	 * applied, so the restore has to reproduce them.  A record that does
+	 * not say the bits were set is restored without them, which is what a
+	 * memfd created under vm.memfd_noexec=2 looks like and is also the safe
+	 * reading for records written before this flag existed.
 	 */
 	file = memfd_alloc_file("", MFD_ALLOW_SEALING);
 	if (IS_ERR(file)) {
@@ -543,6 +557,19 @@ static int memfd_luo_retrieve(struct liveupdate_file_op_args *args)
 		goto free_ser;
 	}
 
+	/*
+	 * Reproduce the exec bits before the seals are re-applied: F_SEAL_EXEC
+	 * freezes the bits as they were at sealing time, and the W^X rule in
+	 * memfd_add_seals() looks at the mode when it decides whether to imply
+	 * the write seals.  Setting the mode here, directly on the fresh
+	 * anonymous inode, is safe -- the file has not been published and no
+	 * other thread can reach it yet.
+	 */
+	if (ser->flags & MEMFD_LUO_FL_EXEC_BIT)
+		file->f_inode->i_mode |= 0111;
+	else
+		file->f_inode->i_mode &= ~0111;
+
 	err = memfd_add_seals(file, ser->seals);
 	if (err) {
 		pr_err("failed to add seals: %pe\n", ERR_PTR(err));
-- 
2.56.0



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

end of thread, other threads:[~2026-10-14  3:13 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-14  0:35 [PATCH] mm/memfd_luo: carry the exec-bit state in the preserved record Haolong Wang
2026-10-14  0:49 ` sashiko-bot
2026-10-14  3:12   ` Haolong Wang

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.