Re: [RFC PATCH v1 1/2] mm/memfd: Add support for F_SEAL_FUTURE_EXEC to memfd

Jeff Xu
2025-01-09T23:30:48Z

On Wed, Jan 8, 2025 at 11:06 AM Lorenzo Stoakes
<lorenzo.stoakes@oracle.com> wrote:
>
> On Mon, Jan 06, 2025 at 04:44:33PM -0800, Kees Cook wrote:
> > On Mon, Jan 06, 2025 at 10:26:27AM -0800, Jeff Xu wrote:
> > > + Kees because this is related to W^X memfd and security.
> > >
> > > On Fri, Jan 3, 2025 at 7:14 AM Jann Horn <jannh@google.com> wrote:
> > > >
> > > > On Fri, Dec 6, 2024 at 7:19 PM Lorenzo Stoakes
> > > > <lorenzo.stoakes@oracle.com> wrote:
> > > > > On Thu, Dec 05, 2024 at 05:09:22PM -0800, Isaac J. Manjarres wrote:
> > > > > > + if (is_exec_sealed(seals)) {
> > > > >
> > > > > Are we intentionally disallowing a MAP_PRIVATE memfd's mapping's execution?
> > > > > I've not tested this scenario so don't know if we somehow disallow this in
> > > > > another way but note on write checks we only care about shared mappings.
> > > > >
> > > > > I mean one could argue that a MAP_PRIVATE situation is the same as copying
> > > > > the data into an anon buffer and doing what you want with it, here you
> > > > > could argue the same...
> > > > >
> > > > > So probably we should only care about VM_SHARED?
> > > >
> > > > FWIW I think it doesn't make sense to distinguish between
> > > > shared/private mappings here - in the scenario described in the cover
> > > > letter, it wouldn't matter that much to an attacker whether the
> > > > mapping is shared or private (as long as the VMA contents haven't been
> > > > CoWed already).
> > > +1 on this.
> > > The concept of blocking this for only shared mapping is questionable.
> >
> > Right -- why does sharedness matter? It seems more robust to me to not
> > create a corner case but rather apply the flag/behavior universally?
> >
>
> I'm struggling to understand what you are protecting against, if I can receive a
> buffer '-not executable-'. But then copy it into another buffer I mapped, and
> execute it?
>
preventing mmap() a memfd has the same threat model as preventing
execve() of a memfd, using execve() of a memfd as an example (since
the kernel already supports this): an attacker wanting to execute a
hijacked memfd must already have the ability to call execve() (e.g.,
by modifying a function pointer or using ROP). To prevent this, the
kernel supports making memfds non-executable (rw-) and permanently
preventing them from becoming executable (sealing with F_SEAL_EXEC).
Once the execve() attack path is blocked, the next thing an attacker
could do is mmap() the memfd into the process's memory and jump to it.

That is the reason I think we could tie this feature with
non-executable-bit + F_SEAL_EXEC, and avoid a new flag. This approach
leverages the existing memfd_create(MFD_NOEXEC_SEAL) function and
related sysctl for system level enforcement. This makes it simple for
applications to use the same function and ensures that both execve()
and mmap() are blocked by non-executable memfd. There is a small
backward compatibility risk for this approach, e.g. an application
already uses MFD_NOEXEC_SEAL but chooses to mmap it as executable, but
I think such a user case is strange to support.

If we're okay with using F_SEAL_FUTURE_EXEC, then share/private don't
matter, as the threat model explains. This flag's generic naming also
suggests it could apply to regular files in future. However, if this
flag is intended solely for shared memfd, it should be renamed to
something like F_SEAL_SHARED_MEMFD_FUTURE_EXEC_MAPPING. I don't think
this is the intention, right ?

LSM such as selinux is another option to consider, maybe it is a
better place to implement this? because selinux policy provides
system level control and enforcement, which the current implementation
lacks. If we end up having a selinux policy for this later, wouldn't
that obsolete this feature ?

-Jeff