// SPDX-License-Identifier: GPL-2.0 /* * Implement mseal() syscall. * * Copyright (c) 2023,2024 Google, Inc. * * Author: Jeff Xu */ #include #include #include #include #include #include #include #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); }