[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