Re: mm/mremap: F_SEAL_FUTURE_WRITE can be bypassed via mremap() VMA expansion/duplication

From: Junyoung Jang

Date: Sat Nov 07 2026 - 10:36:31 EST


On 11/7/26 04:07, ... wrote:
> On 11/7/26 04:07, Junyoung Jang wrote:
> > Hello,
>
> Hi,
>
> >
> > I found a reproducible F_SEAL_FUTURE_WRITE seal-policy bypass. A process
> > holding a one-page shared writable memfd mapping created before the seal can
> > use mremap() after the seal to gain writable access to additional file pages.
>
> F_SEAL_FUTURE_WRITE is not really range-aware. What makes you believe that it
> would be?
>
> I'd say it's expected that if someone has write access to the memfd, it has
> write access to the whole thing, not just some subset that was obtained through
> the initial mmap.
>
> Any documentation that made you believe that it works that way?

Hi,

Thank you for the quick feedback.

My understanding came from the following documentation and commit history:

1. Linux man-pages: F_ADD_SEALS(2const) / fcntl(2) (under "File Sealing" ->
"F_SEAL_FUTURE_WRITE"):

"The effect of this seal is similar to F_SEAL_WRITE, but the contents
of the file can still be modified via shared writable mappings that
were created prior to the seal being set. Any attempt to create a new
writable mapping on the file via mmap(2) will fail with EPERM.
Similarly, an attempt to write to the file via write(2) will fail
with EPERM."

(Reference: https://man7.org/linux/man-pages/man2/F_ADD_SEALS.2const.html)

2. The original commit introducing F_SEAL_FUTURE_WRITE, commit ab3948f58ff8
("mm/memfd: add an F_SEAL_FUTURE_WRITE seal to memfd"), which states
that the seal:

"...prevents any future mmap and write syscalls from succeeding while
keeping the existing mmap active."

(Reference: https://git.kernel.org/linus/ab3948f58ff8)

3. Linux man-pages: mremap(2) (man2/mremap.2, regarding old_size == 0)
and the comment in mm/mremap.c:check_prep_vma():

mremap(2) documents that when old_size is 0 on a shareable mapping,
it behaves by "creating a new mapping". Similarly, check_prep_vma()
notes that "!old_len is a special case where an attempt is made to
'duplicate' the existing mapping".

(Reference: https://man7.org/linux/man-pages/man2/mremap.2.html)

Combining these references, I interpreted the intended invariant to be that
post-seal writes are restricted to the pre-existing mapping itself, rather
than granting the process blanket write access to the entire memfd.

In particular, two behaviors seemed inconsistent with the idea that the
process simply has whole-file write access:

- Even when holding a pre-seal writable mapping on page 1, the same process
cannot call pwrite(2) or a fresh mmap(2) with PROT_WRITE on page 2 (both
fail with -EPERM).
- Calling mremap(2) with old_len=0 creates a brand new VMA covering page 2,
which appears to bypass the restriction against creating a new writable
mapping after the seal.

However, if the intended design in mm is strictly an mmap()-time check—where
any pre-existing VM_MAYWRITE VMA implicitly retains the right to be
duplicated or expanded over unmapped file ranges via mremap(2)—then I
understand why this is considered expected behavior. If that is the case,
perhaps clarifying this in the F_ADD_SEALS(2const) man page would be helpful.

Best regards,
Junyoung Jang <graypanda.inzag@xxxxxxxxx>

On Sat, Nov 7, 2026 at 10:18 PM David Hildenbrand (Arm)
<david@xxxxxxxxxx> wrote:
>
> On 11/7/26 04:07, Junyoung Jang wrote:
> > Hello,
>
> Hi,
>
> >
> > I found a reproducible F_SEAL_FUTURE_WRITE seal-policy bypass. A process
> > holding a one-page shared writable memfd mapping created before the seal can
> > use mremap() after the seal to gain writable access to additional file pages.
>
> F_SEAL_FUTURE_WRITE is not really range-aware. What makes you believe that it
> would be?
>
> I'd say it's expected that if someone has write access to the memfd, it has
> write access to the whole thing, not just some subset that was obtained through
> the initial mmap.
>
> Any documentation that made you believe that it works that way?
>
> --
> Cheers,
>
> David