| // 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); | |
| } |