summaryrefslogtreecommitdiffstats
path: root/mm/mseal.c
diff options
context:
space:
mode:
Diffstat (limited to 'mm/mseal.c')
-rw-r--r--mm/mseal.c143
1 files changed, 143 insertions, 0 deletions
diff --git a/mm/mseal.c b/mm/mseal.c
new file mode 100644
index 000000000..7a8ac66dc
--- /dev/null
+++ b/mm/mseal.c
@@ -0,0 +1,143 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Implement mseal() syscall.
+ *
+ * Copyright (c) 2023,2024 Google, Inc.
+ *
+ * Author: Jeff Xu <jeffxu@chromium.org>
+ */
+
+#include <linux/mempolicy.h>
+#include <linux/minmax.h>
+#include <linux/mman.h>
+#include <linux/mm.h>
+#include <linux/mm_inline.h>
+#include <linux/syscalls.h>
+#include <linux/sched.h>
+#include "internal.h"
+
+static bool range_contains_unmapped(unsigned long start, unsigned long end)
+{
+ VMA_ITERATOR(vmi, current->mm, start);
+ unsigned long prev_end = start;
+ struct vm_area_struct *vma;
+
+ for_each_vma_range(vmi, vma, end) {
+ if (vma->vm_start > prev_end)
+ return true;
+
+ prev_end = vma->vm_end;
+ }
+
+ return prev_end < end;
+}
+
+static int __mseal_range(unsigned long start, unsigned long end)
+{
+ VMA_ITERATOR(vmi, current->mm, start);
+ struct vm_area_struct *vma, *prev;
+
+ /* We know there are no gaps so this will be non-NULL. */
+ vma = vma_iter_load(&vmi);
+ prev = vma_prev(&vmi);
+ if (start > vma->vm_start)
+ prev = vma;
+
+ for_each_vma_range(vmi, vma, end) {
+ const unsigned long curr_start = max(vma->vm_start, start);
+ const unsigned long curr_end = min(vma->vm_end, end);
+
+ if (!vma_test(vma, VMA_SEALED_BIT)) {
+ vma_flags_t vma_flags = vma->flags;
+
+ vma_flags_set(&vma_flags, VMA_SEALED_BIT);
+
+ vma = vma_modify_flags(&vmi, prev, vma, curr_start,
+ curr_end, &vma_flags);
+ if (IS_ERR(vma))
+ return PTR_ERR(vma);
+ vma_start_write(vma);
+ vma_set_flags(vma, VMA_SEALED_BIT);
+ }
+
+ prev = vma;
+ }
+
+ return 0;
+}
+
+static int mseal_range(unsigned long start, unsigned long end)
+{
+ int err;
+
+ err = mmap_write_lock_killable(current->mm);
+ if (err)
+ return err;
+ if (range_contains_unmapped(start, end))
+ err = -ENOMEM;
+ else
+ err = __mseal_range(start, end);
+ mmap_write_unlock(current->mm);
+ return err;
+}
+
+/**
+ * mseal_mmap_page_zero() - If the MMAP_PAGE_ZERO personality is set, mseal()
+ * the page mapped at address zero.
+ */
+void mseal_mmap_page_zero(void)
+{
+ int err;
+
+ if (WARN_ON_ONCE(!(current->personality & MMAP_PAGE_ZERO)))
+ return;
+
+ err = mseal_range(0, PAGE_SIZE);
+ if (err)
+ pr_warn_ratelimited("pid=%d, couldn't seal address 0, ret=%d.\n",
+ task_pid_nr(current), err);
+}
+
+/*
+ * Seal VMAs in the specified input range to prevent an attacker replacing what
+ * is mapped in the range with something else.
+ *
+ * Disallows:
+ * - VMA unmapping, remapping or shrinking.
+ * - Overwriting the VMA with another one via mmap(), mremap() or similar.
+ * - Alteration of properties via mprotect()/pkey_mprotect().
+ * - Destructive madvise() behaviours (like MADV_DONTNEED) on anonymous read-only
+ * ranges.
+ *
+ * Since unmapped ranges can be mapped at any time, the input range must span
+ * mapped ranges only.
+ *
+ * The flags parameter is currently reserved.
+ */
+SYSCALL_DEFINE3(mseal, unsigned long, start, size_t, len, unsigned long, flags)
+{
+ size_t len_aligned;
+ unsigned long end;
+
+ /* Verify flags not set. */
+ if (flags)
+ return -EINVAL;
+
+ start = untagged_addr(start);
+ if (!PAGE_ALIGNED(start))
+ return -EINVAL;
+
+ len_aligned = PAGE_ALIGN(len);
+ /* Check to see whether len was rounded up from small -ve to zero. */
+ if (len && !len_aligned)
+ return -EINVAL;
+
+ end = start + len_aligned;
+ if (end < start)
+ return -EINVAL;
+
+ if (end == start)
+ return 0;
+
+ return mseal_range(start, end);
+}