* [RFC PATCH v2 0/2] Add file seal to prevent future exec mappings
@ 2024-12-27 1:51 Isaac J. Manjarres
2024-12-27 1:51 ` [RFC PATCH v2 1/2] mm/memfd: Add support for F_SEAL_FUTURE_EXEC to memfd Isaac J. Manjarres
2024-12-27 1:52 ` [RFC PATCH v2 2/2] selftests/memfd: Add tests for F_SEAL_FUTURE_EXEC Isaac J. Manjarres
0 siblings, 2 replies; 4+ messages in thread
From: Isaac J. Manjarres @ 2024-12-27 1:51 UTC (permalink / raw)
To: Jeff Layton, Chuck Lever, Alexander Aring, Andrew Morton, Shuah Khan
Cc: surenb, kaleshsingh, jstultz, aliceryhl, jeffxu, kees,
Isaac J. Manjarres, kernel-team, linux-fsdevel, linux-kernel,
linux-mm, linux-kselftest
Android uses the ashmem driver [1] for creating shared memory regions
between processes. The ashmem driver exposes an ioctl command for
processes to restrict the permissions an ashmem buffer can be mapped
with.
Buffers are created with the ability to be mapped as readable, writable,
and executable. Processes remove the ability to map some ashmem buffers
as executable to ensure that those buffers cannot be used to inject
malicious code for another process to run. Other buffers retain their
ability to be mapped as executable, as these buffers can be used for
just-in-time (JIT) compilation. So there is a need to be able to remove
the ability to map a buffer as executable on a per-buffer basis.
Android is currently trying to migrate towards replacing its ashmem
driver usage with memfd. Part of the transition involved introducing a
library that serves to abstract away how shared memory regions are
allocated (i.e. ashmem vs memfd). This allows clients to use a single
interface for restricting how a buffer can be mapped without having to
worry about how it is handled for ashmem (through the ioctl
command mentioned earlier) or memfd (through file seals).
While memfd has support for preventing buffers from being mapped as
writable beyond a certain point in time (thanks to
F_SEAL_FUTURE_WRITE), it does not have a similar interface to prevent
buffers from being mapped as executable beyond a certain point.
However, that could be implemented as a file seal (F_SEAL_FUTURE_EXEC)
which works similarly to F_SEAL_FUTURE_WRITE.
F_SEAL_FUTURE_WRITE was chosen as a template for how this new seal
should behave, instead of F_SEAL_WRITE, for the following reasons:
1. Having the new seal behave like F_SEAL_FUTURE_WRITE matches the
behavior that was present with ashmem. This aids in seamlessly
transitioning clients away from ashmem to memfd.
2. Making the new seal behave like F_SEAL_WRITE would mean that no
mappings that could become executable in the future (i.e. via
mprotect()) can exist when the seal is applied. However, there are
known cases (e.g. CursorWindow [2]) where restrictions are applied
on how a buffer can be mapped after a mapping has already been made.
That mapping may have VM_MAYEXEC set, which would not allow the seal
to be applied successfully.
Therefore, the F_SEAL_FUTURE_EXEC seal was designed to have the same
semantics as F_SEAL_FUTURE_WRITE.
Note: this series depends on Lorenzo's work [3], [4], [5] from Andrew
Morton's mm-unstable branch [6], which reworks memfd's file seal checks,
allowing for newer file seals to be implemented in a cleaner fashion.
Changes from v1 ==> v2:
- Changed the return code to be -EPERM instead of -EACCES when
attempting to map an exec sealed file with PROT_EXEC to align
to mmap()'s man page. Thank you Kalesh Singh for spotting this!
- Rebased on top of Lorenzo's work to cleanup memfd file seal checks in
mmap() ([3], [4], and [5]). Thank you for this Lorenzo!
- Changed to deny PROT_EXEC mappings only if the mapping is shared,
instead of for both shared and private mappings, after discussing
this with Lorenzo.
Opens:
- Lorenzo brought up that this patch may negatively impact the usage of
MFD_NOEXEC_SCOPE_NOEXEC_ENFORCED [7]. However, it is not clear to me
why that is the case. At the moment, my intent is for the executable
permissions of the file to be disjoint from the ability to create
executable mappings.
Links:
[1] https://cs.android.com/android/kernel/superproject/+/common-android-mainline:common/drivers/staging/android/ashmem.c
[2] https://developer.android.com/reference/android/database/CursorWindow
[3] https://lore.kernel.org/all/cover.1732804776.git.lorenzo.stoakes@oracle.com/
[4] https://lkml.kernel.org/r/20241206212846.210835-1-lorenzo.stoakes@oracle.com
[5] https://lkml.kernel.org/r/7dee6c5d-480b-4c24-b98e-6fa47dbd8a23@lucifer.local
[6] https://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm.git/tree/?h=mm-unstable
[7] https://lore.kernel.org/all/3a53b154-1e46-45fb-a559-65afa7a8a788@lucifer.local/
Links to previous versions:
v1: https://lore.kernel.org/all/20241206010930.3871336-1-isaacmanjarres@google.com/
Isaac J. Manjarres (2):
mm/memfd: Add support for F_SEAL_FUTURE_EXEC to memfd
selftests/memfd: Add tests for F_SEAL_FUTURE_EXEC
include/uapi/linux/fcntl.h | 1 +
mm/memfd.c | 39 ++++++++++-
tools/testing/selftests/memfd/memfd_test.c | 79 ++++++++++++++++++++++
3 files changed, 118 insertions(+), 1 deletion(-)
--
2.47.1.613.gc27f4b7a9f-goog
^ permalink raw reply [flat|nested] 4+ messages in thread
* [RFC PATCH v2 1/2] mm/memfd: Add support for F_SEAL_FUTURE_EXEC to memfd
2024-12-27 1:51 [RFC PATCH v2 0/2] Add file seal to prevent future exec mappings Isaac J. Manjarres
@ 2024-12-27 1:51 ` Isaac J. Manjarres
2024-12-27 1:52 ` [RFC PATCH v2 2/2] selftests/memfd: Add tests for F_SEAL_FUTURE_EXEC Isaac J. Manjarres
1 sibling, 0 replies; 4+ messages in thread
From: Isaac J. Manjarres @ 2024-12-27 1:51 UTC (permalink / raw)
To: Jeff Layton, Chuck Lever, Alexander Aring, Andrew Morton, Shuah Khan
Cc: surenb, kaleshsingh, jstultz, aliceryhl, jeffxu, kees,
Isaac J. Manjarres, kernel-team, linux-fsdevel, linux-kernel,
linux-mm, linux-kselftest
Android currently uses the ashmem driver [1] for creating shared memory
regions between processes. Ashmem buffers can initially be mapped with
PROT_READ, PROT_WRITE, and PROT_EXEC. Processes can then use the
ASHMEM_SET_PROT_MASK ioctl command to restrict--never add--the
permissions that the buffer can be mapped with.
Processes can remove the ability to map ashmem buffers as executable to
ensure that those buffers cannot be exploited to run unintended code.
For instance, suppose process A allocates a memfd that is meant to be
read and written by itself and another process, call it B.
Process A shares the buffer with process B, but process B injects code
into the buffer, and compromises process A, such that it makes A map
the buffer with PROT_EXEC. This provides an opportunity for process A
to run the code that process B injected into the buffer.
If process A had the ability to seal the buffer against future
executable mappings before sharing the buffer with process B, this
attack would not be possible.
Android is currently trying to replace ashmem with memfd. However, memfd
does not have a provision to permanently remove the ability to map a
buffer as executable, and leaves itself open to the type of attack
described earlier. However, this should be something that can be
achieved via a new file seal.
There are known usecases (e.g. CursorWindow [2]) where a process
maps a buffer with read/write permissions before restricting the buffer
to being mapped as read-only for future mappings.
The resulting VMA from the writable mapping has VM_MAYEXEC set, meaning
that mprotect() can change the mapping to be executable. Therefore,
implementing the seal similar to F_SEAL_WRITE would not be appropriate,
since it would not work with the CursorWindow usecase. This is because
the CursorWindow process restricts the mapping permissions to read-only
after the writable mapping is created. So, adding a file seal for
executable mappings that operates like F_SEAL_WRITE would fail.
Therefore, add support for F_SEAL_FUTURE_EXEC, which is handled
similarly to F_SEAL_FUTURE_WRITE. This ensures that CursorWindow can
continue to create a writable mapping initially, and then restrict the
permissions on the buffer to be mappable as read-only by using both
F_SEAL_FUTURE_WRITE and F_SEAL_FUTURE_EXEC. After the seal is
applied, any calls to mmap() with PROT_EXEC will fail.
[1] https://cs.android.com/android/kernel/superproject/+/common-android-mainline:common/drivers/staging/android/ashmem.c
[2] https://developer.android.com/reference/android/database/CursorWindow
Signed-off-by: Isaac J. Manjarres <isaacmanjarres@google.com>
---
include/uapi/linux/fcntl.h | 1 +
mm/memfd.c | 39 +++++++++++++++++++++++++++++++++++++-
2 files changed, 39 insertions(+), 1 deletion(-)
diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h
index 6e6907e63bfc..ef066e524777 100644
--- a/include/uapi/linux/fcntl.h
+++ b/include/uapi/linux/fcntl.h
@@ -49,6 +49,7 @@
#define F_SEAL_WRITE 0x0008 /* prevent writes */
#define F_SEAL_FUTURE_WRITE 0x0010 /* prevent future writes while mapped */
#define F_SEAL_EXEC 0x0020 /* prevent chmod modifying exec bits */
+#define F_SEAL_FUTURE_EXEC 0x0040 /* prevent future executable mappings */
/* (1U << 31) is reserved for signed error codes */
/*
diff --git a/mm/memfd.c b/mm/memfd.c
index 5f5a23c9051d..cfd62454df5e 100644
--- a/mm/memfd.c
+++ b/mm/memfd.c
@@ -184,6 +184,7 @@ static unsigned int *memfd_file_seals_ptr(struct file *file)
}
#define F_ALL_SEALS (F_SEAL_SEAL | \
+ F_SEAL_FUTURE_EXEC |\
F_SEAL_EXEC | \
F_SEAL_SHRINK | \
F_SEAL_GROW | \
@@ -357,14 +358,50 @@ static int check_write_seal(unsigned long *vm_flags_ptr)
return 0;
}
+static inline bool is_exec_sealed(unsigned int seals)
+{
+ return seals & F_SEAL_FUTURE_EXEC;
+}
+
+static int check_exec_seal(unsigned long *vm_flags_ptr)
+{
+ unsigned long vm_flags = *vm_flags_ptr;
+ unsigned long mask = vm_flags & (VM_SHARED | VM_EXEC);
+
+ /* Executability is not a concern for private mappings. */
+ if (!(mask & VM_SHARED))
+ return 0;
+
+ /*
+ * New PROT_EXEC and MAP_SHARED mmaps are not allowed when exec seal
+ * is active.
+ */
+ if (mask & VM_EXEC)
+ return -EPERM;
+
+ /*
+ * Prevent mprotect() from making an exec-sealed mapping executable in
+ * the future.
+ */
+ *vm_flags_ptr &= ~VM_MAYEXEC;
+
+ return 0;
+}
+
int memfd_check_seals_mmap(struct file *file, unsigned long *vm_flags_ptr)
{
int err = 0;
unsigned int *seals_ptr = memfd_file_seals_ptr(file);
unsigned int seals = seals_ptr ? *seals_ptr : 0;
- if (is_write_sealed(seals))
+ if (is_write_sealed(seals)) {
err = check_write_seal(vm_flags_ptr);
+ if (err)
+ return err;
+ }
+
+ if (is_exec_sealed(seals))
+ err = check_exec_seal(vm_flags_ptr);
return err;
}
--
2.47.1.613.gc27f4b7a9f-goog
^ permalink raw reply [flat|nested] 4+ messages in thread
* [RFC PATCH v2 2/2] selftests/memfd: Add tests for F_SEAL_FUTURE_EXEC
2024-12-27 1:51 [RFC PATCH v2 0/2] Add file seal to prevent future exec mappings Isaac J. Manjarres
2024-12-27 1:51 ` [RFC PATCH v2 1/2] mm/memfd: Add support for F_SEAL_FUTURE_EXEC to memfd Isaac J. Manjarres
@ 2024-12-27 1:52 ` Isaac J. Manjarres
2025-01-10 11:37 ` Alice Ryhl
1 sibling, 1 reply; 4+ messages in thread
From: Isaac J. Manjarres @ 2024-12-27 1:52 UTC (permalink / raw)
To: Jeff Layton, Chuck Lever, Alexander Aring, Andrew Morton, Shuah Khan
Cc: surenb, kaleshsingh, jstultz, aliceryhl, jeffxu, kees,
Isaac J. Manjarres, kernel-team, linux-fsdevel, linux-kernel,
linux-mm, linux-kselftest
Add tests to ensure that F_SEAL_FUTURE_EXEC behaves as expected.
Signed-off-by: Isaac J. Manjarres <isaacmanjarres@google.com>
---
tools/testing/selftests/memfd/memfd_test.c | 79 ++++++++++++++++++++++
1 file changed, 79 insertions(+)
diff --git a/tools/testing/selftests/memfd/memfd_test.c b/tools/testing/selftests/memfd/memfd_test.c
index c0c53451a16d..abc213a5ce99 100644
--- a/tools/testing/selftests/memfd/memfd_test.c
+++ b/tools/testing/selftests/memfd/memfd_test.c
@@ -31,6 +31,7 @@
#define STACK_SIZE 65536
#define F_SEAL_EXEC 0x0020
+#define F_SEAL_FUTURE_EXEC 0x0040
#define F_WX_SEALS (F_SEAL_SHRINK | \
F_SEAL_GROW | \
@@ -318,6 +319,37 @@ static void *mfd_assert_mmap_private(int fd)
return p;
}
+static void *mfd_fail_mmap_exec(int fd)
+{
+ void *p;
+
+ p = mmap(NULL,
+ mfd_def_size,
+ PROT_EXEC,
+ MAP_SHARED,
+ fd,
+ 0);
+ if (p != MAP_FAILED) {
+ printf("mmap() didn't fail as expected\n");
+ abort();
+ }
+
+ return p;
+}
+
+static void mfd_fail_mprotect_exec(void *p)
+{
+ int ret;
+
+ ret = mprotect(p,
+ mfd_def_size,
+ PROT_EXEC);
+ if (!ret) {
+ printf("mprotect didn't fail as expected\n");
+ abort();
+ }
+}
+
static int mfd_assert_open(int fd, int flags, mode_t mode)
{
char buf[512];
@@ -998,6 +1030,52 @@ static void test_seal_future_write(void)
close(fd);
}
+/*
+ * Test SEAL_FUTURE_EXEC_MAPPING
+ * Test whether SEAL_FUTURE_EXEC_MAPPING actually prevents executable mappings.
+ */
+static void test_seal_future_exec_mapping(void)
+{
+ int fd;
+ void *p;
+
+
+ printf("%s SEAL-FUTURE-EXEC-MAPPING\n", memfd_str);
+
+ fd = mfd_assert_new("kern_memfd_seal_future_exec_mapping",
+ mfd_def_size,
+ MFD_CLOEXEC | MFD_ALLOW_SEALING);
+
+ /*
+ * PROT_READ | PROT_WRITE mappings create VMAs with VM_MAYEXEC set.
+ * However, F_SEAL_FUTURE_EXEC applies to subsequent mappings,
+ * so it should still succeed even if this mapping is active when the
+ * seal is applied.
+ */
+ p = mfd_assert_mmap_shared(fd);
+
+ mfd_assert_has_seals(fd, 0);
+
+ mfd_assert_add_seals(fd, F_SEAL_FUTURE_EXEC);
+ mfd_assert_has_seals(fd, F_SEAL_FUTURE_EXEC);
+
+ mfd_fail_mmap_exec(fd);
+
+ munmap(p, mfd_def_size);
+
+ /* Ensure that new mappings without PROT_EXEC work. */
+ p = mfd_assert_mmap_shared(fd);
+
+ /*
+ * Ensure that mappings created after the seal was applied cannot be
+ * made executable via mprotect().
+ */
+ mfd_fail_mprotect_exec(p);
+
+ munmap(p, mfd_def_size);
+ close(fd);
+}
+
static void test_seal_write_map_read_shared(void)
{
int fd;
@@ -1639,6 +1717,7 @@ int main(int argc, char **argv)
test_seal_shrink();
test_seal_grow();
test_seal_resize();
+ test_seal_future_exec_mapping();
if (pid_ns_supported()) {
test_sysctl_simple();
--
2.47.1.613.gc27f4b7a9f-goog
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [RFC PATCH v2 2/2] selftests/memfd: Add tests for F_SEAL_FUTURE_EXEC
2024-12-27 1:52 ` [RFC PATCH v2 2/2] selftests/memfd: Add tests for F_SEAL_FUTURE_EXEC Isaac J. Manjarres
@ 2025-01-10 11:37 ` Alice Ryhl
0 siblings, 0 replies; 4+ messages in thread
From: Alice Ryhl @ 2025-01-10 11:37 UTC (permalink / raw)
To: Isaac J. Manjarres
Cc: Jeff Layton, Chuck Lever, Alexander Aring, Andrew Morton,
Shuah Khan, surenb, kaleshsingh, jstultz, jeffxu, kees,
kernel-team, linux-fsdevel, linux-kernel, linux-mm,
linux-kselftest
On Fri, Dec 27, 2024 at 2:52 AM Isaac J. Manjarres
<isaacmanjarres@google.com> wrote:
>
> Add tests to ensure that F_SEAL_FUTURE_EXEC behaves as expected.
>
> Signed-off-by: Isaac J. Manjarres <isaacmanjarres@google.com>
Reviewed-by: Alice Ryhl <aliceryhl@google.com>
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2025-01-10 11:37 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-12-27 1:51 [RFC PATCH v2 0/2] Add file seal to prevent future exec mappings Isaac J. Manjarres
2024-12-27 1:51 ` [RFC PATCH v2 1/2] mm/memfd: Add support for F_SEAL_FUTURE_EXEC to memfd Isaac J. Manjarres
2024-12-27 1:52 ` [RFC PATCH v2 2/2] selftests/memfd: Add tests for F_SEAL_FUTURE_EXEC Isaac J. Manjarres
2025-01-10 11:37 ` Alice Ryhl
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox