Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Junyoung Jang" <graypanda.inzag@gmail.com>,
	"Andrew Morton" <akpm@linux-foundation.org>,
	"Liam R . Howlett" <liam@infradead.org>,
	"Lorenzo Stoakes" <ljs@kernel.org>,
	"Hugh Dickins" <hughd@google.com>,
	"David Hildenbrand" <david@kernel.org>
Cc: "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
Subject: Re: mm/mremap: F_SEAL_FUTURE_WRITE can be bypassed via mremap() VMA expansion/duplication
Date: Sun, 08 Nov 2026 03:07:44 +0000	[thread overview]
Message-ID: <sashiko-outbox-203536@kernel.org> (raw)
In-Reply-To: <20261107030708.1031977-1-graypanda.inzag@gmail.com>

Thank you for your contribution! Sashiko AI review found 3 potential
issues to consider:
- [Low] mm/mremap: check_prep_vma() checks memfd seals before VMA bounds
- [Low] mm/mremap: check_prep_vma() open-codes vma_is_shared_maywrite()
- [Low] mm/mremap: commit message is a raw bug report and lacks trailers
--

commit cc4a27e7580c449c0e2b8a3507c3d641a688a645
Author: Junyoung Jang <graypanda.inzag@gmail.com>

> mm/mremap: F_SEAL_FUTURE_WRITE can be bypassed via mremap() VMA expansion/duplication
>
> Hello,
>
> 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.
>
> In the accompanying two-UID reproducer, the second page belongs to a memfd
> created by a different unprivileged user. While a new writable mmap() and
> pwrite() to that second page both fail with -EPERM, the mremap() path
> succeeds and modifies the page's contents.
>
> Affected tree tested:
>
> - Linux 7.4.0-rc1-gd48723711bae, built directly from upstream commit
>   d48723711bae9b8cdc56719c448ba42fef28cbee using x86_64_defconfig.
> - A second image built from the same commit with the proposed mm/mremap.c
>   diff, reported by uname as 7.4.0-rc1-gd48723711bae-dirty.
> - Each before/after pair was booted in QEMU with the same statically
>   linked C PoC.
>
> Root cause:
>
> The missing seal check is in mm/mremap.c:check_prep_vma() (lines 1808-1872
> of the tested commit). mmap() calls memfd_check_seals_mmap() at
> mm/mmap.c:499; for F_SEAL_FUTURE_WRITE, mm/memfd.c:384-385 rejects a
> new shared writable mapping with EPERM. check_prep_vma() has no equivalent
> check before it permits a larger mapping.
>
> For the PoC's old_len=0 call, this condition at mm/mremap.c:1819 only
> rejects duplication of a non-shared VMA:
>
>     if (!old_len && !vma_test_any(vma, VMA_SHARED_BIT, VMA_MAYSHARE_BIT)) {
>
> It is false for the PoC's MAP_SHARED VMA. check_prep_vma() then checks
> VMA bounds and expansion limits and returns 0 at mm/mremap.c:1872 without
> checking vma->vm_file for write seals. For this zero-length call,
> expand_vma() moves the VMA and reaches copy_vma_and_data(), which calls
> copy_vma() with the larger new_len (mm/mremap.c:1293-1295):
>
>     new_vma = copy_vma(&vma, vrm->new_addr, vrm->new_len, new_pgoff,
>                        new_anon_pgoff, &pmc.need_rmap_locks,
>                        vrm->flags & MREMAP_DONTUNMAP);
>
> copy_vma() can duplicate the original VMA and extend its range
> (mm/vma.c:2052-2055), retaining VM_WRITE and VM_MAYWRITE. The newly
> covered second memfd page is writable despite F_SEAL_FUTURE_WRITE.
> Ordinary expansion with old_len=one page also succeeded on the unpatched
> kernel. A read-only shared mapping with VM_MAYWRITE can likewise be
> expanded and then changed to PROT_WRITE with mprotect().
>
> Impact:
>
> This allows a process to write to file pages outside its pre-seal mapping
> across a UID boundary, affecting scenarios where a memfd owner grants
> limited writable access to a specific range before sealing and relies on
> F_SEAL_FUTURE_WRITE to prevent future writable mappings of other pages.
>
> Reproducer summary:
>
> 1. Start as root only to set up two distinct unprivileged UIDs (10002 for
>    the owner, 10001 for the writer); both drop all capabilities.
> 2. UID 10002 creates a two-page memfd and initializes page two to 'A'.
> 3. UID 10002 passes the fd to UID 10001 via SCM_RIGHTS.
> 4. UID 10001 maps only page one with MAP_SHARED and PROT_WRITE.
> 5. UID 10002 adds F_SEAL_FUTURE_WRITE.
> 6. UID 10001 confirms a fresh writable mmap() of page two and pwrite() to it
>    both fail with -EPERM.
> 7. UID 10001 calls mremap(old_addr, 0, 2 * page_size, MREMAP_MAYMOVE),
>    writes 'Z' through the expanded mapping, and UID 10002 reads 'Z' back
>    from the second file page.
>
> Build and run:
>   $ cc -O0 -g -Wall -Wextra -Werror -static memfd_future_write_mremap_poc.c -o poc
>   $ sudo ./poc
>   writer uid=10001; mmap=EPERM; pwrite=EPERM; mremap write=Z; result=63
>   owner uid=10002; file page 2=Z; size=8192; result=255
>
> Suggested fix direction:
>
> I believe mremap() needs to check the memfd write seal before duplicating
> (old_len=0) or expanding (new_len > old_len) a shared VMA that has
> VM_MAYWRITE.
>
> I tested the rough diff below in check_prep_vma() on the same commit and
> confirmed that it blocks the reproducer (mremap fails with -EPERM). However,
> I am sending this as a bug report rather than a formal [PATCH] because I am
> unsure about potential side effects:
>
> 1. Because a PROT_READ shared mapping with VM_MAYWRITE could be expanded and
>    later made writable via mprotect(), the diff temporarily sets VMA_WRITE_BIT
>    on a local flags copy to force memfd_check_seals_mmap() to return -EPERM.
>    However, this rejects legitimate read-only mremap() expansions outright,
>    whereas mmap() would normally allow a read-only mapping and simply clear
>    VM_MAYWRITE.
> 2. I am not certain whether check_prep_vma() is the right place regarding
>    locking/synchronization against a concurrent F_ADD_SEALS call.
>
> I would appreciate your guidance on the proper way to handle this, or feel
> free to take it from here:

[Severity: Low]
The commit message is formatted as a conversational bug report rather than
a kernel changelog and is missing a Signed-off-by: trailer.  It should also
include:

  Fixes: ab3948f58ff8 ("mm/memfd: add an F_SEAL_FUTURE_WRITE seal to memfd")

> diff --git a/mm/mremap.c b/mm/mremap.c
> index 19398d5fca585..cc24716120d20 100644
> --- a/mm/mremap.c
> +++ b/mm/mremap.c
[ ... ]
> @@ -1829,6 +1830,17 @@ static int check_prep_vma(struct vma_remap_struct *vrm)
>  
>  	old_len = vrm->old_len;
>  	new_len = vrm->new_len;
> +	if (new_len > old_len && vma->vm_file &&
> +	    vma_test_all(vma, VMA_SHARED_BIT, VMA_MAYWRITE_BIT)) {
> +		vma_flags_t flags = vma->flags;
> +		int err;
> +
> +		/* A larger VMA could become writable via mprotect(). */
> +		vma_flags_set(&flags, VMA_WRITE_BIT);
> +		err = memfd_check_seals_mmap(vma->vm_file, &flags);
> +		if (err)
> +			return err;
> +	}

[Severity: Low]
Should this check be placed in the expansion section of check_prep_vma()
after the VMA bounds check?

Running it here causes mremap() with an out-of-bounds old_len
(old_len > vma->vm_end - addr) and new_len > old_len on a write-sealed
shared memfd mapping to return -EPERM instead of -EFAULT.  Moving it past
the old_len == new_len early return also avoids duplicating the
new_len > old_len check.

[Severity: Low]
This isn't a bug, but vma_test_all(vma, VMA_SHARED_BIT, VMA_MAYWRITE_BIT)
open-codes the existing vma_is_shared_maywrite(vma) helper.  In addition,
memfd_check_seals_mmap() is meant to mutate a new VMA's flags during
mmap(), so copying vma->flags and setting a dummy VMA_WRITE_BIT just to
trigger its -EPERM branch is awkward compared to a dedicated memfd helper.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261107030708.1031977-1-graypanda.inzag@gmail.com?part=1


      parent reply	other threads:[~2026-11-08  3:07 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-07  3:07 mm/mremap: F_SEAL_FUTURE_WRITE can be bypassed via mremap() VMA expansion/duplication 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)
2026-11-08  1:27       ` Junyoung Jang
2026-11-08  3:07 ` sashiko-bot [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=sashiko-outbox-203536@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=akpm@linux-foundation.org \
    --cc=baolin.wang@linux.alibaba.com \
    --cc=david@kernel.org \
    --cc=graypanda.inzag@gmail.com \
    --cc=hughd@google.com \
    --cc=jannh@google.com \
    --cc=liam@infradead.org \
    --cc=linux-mm@kvack.org \
    --cc=ljs@kernel.org \
    --cc=pfalcato@suse.de \
    --cc=sashiko-reviews@lists.linux.dev \
    --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