mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: "David Hildenbrand (Arm)" <david@kernel.org>
To: Junyoung Jang <graypanda.inzag@gmail.com>,
	Lorenzo Stoakes <ljs@kernel.org>
Cc: Andrew Morton <akpm@linux-foundation.org>,
	"Liam R . Howlett" <liam@infradead.org>,
	Hugh Dickins <hughd@google.com>,
	Vlastimil Babka <vbabka@kernel.org>, Jann Horn <jannh@google.com>,
	Pedro Falcato <pfalcato@suse.de>,
	Baolin Wang <baolin.wang@linux.alibaba.com>,
	linux-mm@kvack.org, linux-kernel@vger.kernel.org
Subject: Re: mm/mremap: F_SEAL_FUTURE_WRITE can be bypassed via mremap() VMA expansion/duplication
Date: Sat, 7 Nov 2026 19:16:15 +0100	[thread overview]
Message-ID: <c15e171b-d0c2-4808-8d42-e5a650b03150@kernel.org> (raw)
In-Reply-To: <CAO7ntTQGLNtHiu3SY+6HaRZi5gimqjbmw_FoeTSRYpqi1EbnbQ@mail.gmail.com>

On 11/7/26 16:19, Junyoung Jang wrote:
> 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,

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."

Note that "mapping" here means "file mapping" not "folio/page mapping".

> 
>    (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."

Right. The thing is that somebody that didn't have write access to the file
before (through an existing mmap()) won't be able to gain write access (through
a new mmap/write) later.

Expanding mmap() or mremap() modify an *existing* mapping, they don't create a
new one. I think we don't guarantee that expanding mmap works, but mremap() must
work.

> 
>    (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".

Right, the relevant part here is:

	old = mmap(NULL, page, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

and later (after sealing):

	expanded = mremap(old, 0, 2 * page, MREMAP_MAYMOVE);

With your reproducer:

$ sudo ./memfd_future_write_mremap_poc
writer uid=10001; mmap=EPERM; pwrite=EPERM; mremap write=Z; result=63
owner uid=10002; file page 2=Z; size=8192; result=255

Assuming I specify instead

	expanded = mremap(old, page, 2 * page, MREMAP_MAYMOVE);$

$ sudo ./memfd_future_write_mremap_poc
writer uid=10001; mmap=EPERM; pwrite=EPERM; mremap write=Z; result=63
owner uid=10002; file page 2=Z; size=8192; result=255

So same result. What am I missing?

> 
>    (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.

Again, mapping talks about "file mapping", not "page/folio page-table mapping".

> 
> 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).

Right, but the main challenge here is rather figuring out whether the process
already had access. We'd have to scan existing VMAs which is just unwarranted
complexity for something nobody really needs ;)

> - 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.

Correct me if I'm wrong, the only confusion is about the mremap() doc saying
"old_len=0 creates a brand new VMA covering page 2".

And you seem to assume that "new VMA" implies "must be rejected", whereby we
already have a writable VMA there and there is not really a reason to reject it.

-- 
Cheers,

David

      reply	other threads:[~2026-11-07 18:16 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-07  3:07 Junyoung Jang
2026-11-07 13:17 ` David Hildenbrand (Arm)
2026-11-07 15:19   ` Junyoung Jang
2026-11-07 18:16     ` David Hildenbrand (Arm) [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=c15e171b-d0c2-4808-8d42-e5a650b03150@kernel.org \
    --to=david@kernel.org \
    --cc=akpm@linux-foundation.org \
    --cc=baolin.wang@linux.alibaba.com \
    --cc=graypanda.inzag@gmail.com \
    --cc=hughd@google.com \
    --cc=jannh@google.com \
    --cc=liam@infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=ljs@kernel.org \
    --cc=pfalcato@suse.de \
    --cc=vbabka@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®