[BUG] mm/memfd_luo: MFD_NOEXEC_SEAL memfd becomes executable after restore

Haolong Wang 18766350203 at 163.com
Thu Oct 15 17:19:00 PDT 2026


Hi,

I observed: LUO preserves and restores the seals of a memfd, including
F_SEAL_EXEC, but it does not preserve the inode execute mask protected
by that seal. For a new memfd created with MFD_NOEXEC_SEAL, when it has
not been fchmod'ed, its state before preservation is normally
inode->i_mode & 0777 == 0666, seals = 0x20; however, the new inode
created when LUO restores the memfd has default permission bits 0777,
and the seals also change to 0x3e during restoration.

Although the LUO documentation states that it is not a completely
transparent restoration and that only the listed properties are
guaranteed to be preserved, I think that preserving only F_SEAL_EXEC
without preserving the inode mode, or rather the execute mask, is
somewhat unreasonable. I am not sure whether this matches your intended
design.

To put it somewhat imprecisely, I think the LUO mechanism may bypass
the "no executable memfd may be created" expectation that
vm.memfd_noexec=2 is intended to provide.

The analysis is as follows:

1. If we pass the MFD_NOEXEC_SEAL flag, SYSCALL_DEFINE2 goes through
   the sanitize_flags() check, which may modify flags, and then enters
   memfd_alloc_file().

2. In memfd_alloc_file(), it enters shmem_file_setup() and then
   __shmem_file_setup():

```
	if (flags & MFD_HUGETLB) {
		file = hugetlb_file_setup(name, 0, mk_vma_flags(VMA_NORESERVE_BIT),
					HUGETLB_ANONHUGE_INODE,
					(flags >> MFD_HUGE_SHIFT) &
					MFD_HUGE_MASK);
	} else {
		file = shmem_file_setup(name, 0, mk_vma_flags(VMA_NORESERVE_BIT));
	}
```

3. In __shmem_file_setup(), the inode->i_mode obtained by default is
   0777:

```
	inode = shmem_get_inode(&nop_mnt_idmap, mnt->mnt_sb, NULL,
				S_IFREG | S_IRWXUGO, 0, flags);
```

4. Back in memfd_alloc_file(), inode->i_mode becomes 0666, and
   F_SEAL_EXEC is set:

```
	if (flags & MFD_NOEXEC_SEAL) {
		inode->i_mode &= ~0111;
		file_seals = memfd_file_seals_ptr(file);
		if (file_seals) {
			*file_seals &= ~F_SEAL_SEAL;
			*file_seals |= F_SEAL_EXEC;
		}
	} else if (flags & MFD_ALLOW_SEALING) {
		/* MFD_EXEC and MFD_ALLOW_SEALING are set */
		file_seals = memfd_file_seals_ptr(file);
		if (file_seals)
			*file_seals &= ~F_SEAL_SEAL;
	}
```

5. In memfd_luo_preserve(), LUO obtains the seals of the original
   memfd through memfd_get_seals() and stores them in ser->seals:

```
	seals = memfd_get_seals(args->file);
	...
	ser->seals = seals;
```

6. After switching kernels and restoring the memfd, in the
   memfd_luo_retrieve() function, memfd_luo_retrieve() directly calls
   memfd_alloc_file("", MFD_ALLOW_SEALING). This call creates a new
   shmem inode with permission bits 0777. Since MFD_NOEXEC_SEAL is not
   passed and only MFD_ALLOW_SEALING is, the execute bits are not
   cleared, and the inode mode still remains 0777:

```
	file = memfd_alloc_file("", MFD_ALLOW_SEALING);
```

7. Then, in the memfd_luo_retrieve() function, it enters memfd_add_seals():

```
	err = memfd_add_seals(file, ser->seals);
```

8. In memfd_add_seals(), ser->seals holds the F_SEAL_EXEC that the
   original memfd has because of MFD_NOEXEC_SEAL. Since the restored
   inode is 0777, inode->i_mode & 0111 is true, and
   SHRINK|GROW|WRITE|FUTURE_WRITE are additionally added -> seals
   becomes 0x3e:

```
	if (seals & F_SEAL_EXEC && inode->i_mode & 0111)
		seals |= F_SEAL_SHRINK|F_SEAL_GROW|F_SEAL_WRITE|F_SEAL_FUTURE_WRITE;
```

Meanwhile, I noticed the earlier review discussion about the original
seal-preservation patch:

https://lkml.org/lkml/2026/1/26/843
https://lkml.org/lkml/2026/1/26/1044

I feel that your discussion touched on this issue.

I think Pratyush has already come close to the essence of the problem:
"But I think that should be done by preserving the mode of the inode
directly, and then copying the seals back." Unfortunately, that
requirement did not make it into the final implementation.

Regards,
Haolong Wang




More information about the kexec mailing list