mirror of
https://github.com/torvalds/linux.git
synced 2026-09-13 15:40:03 +02:00
There's no need to abstract do_mseal() any longer so put the system call implementation in the system call declaration. The comment around do_mseal() is strangely formatted, overly long and adds a lot of superfluous information that the code already provides, so boil it down to the essentials. Link: https://lore.kernel.org/20260717-mseal-fixups-v2-3-0daa0014b813@kernel.org Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org> Acked-by: David Hildenbrand (Arm) <david@kernel.org> Reviewed-by: Pedro Falcato <pfalcato@suse.de> Cc: Al Viro <viro@zeniv.linux.org.uk> Cc: Christian Brauner <brauner@kernel.org> Cc: Jan Kara <jack@suse.cz> Cc: Jann Horn <jannh@google.com> Cc: Kees Cook <kees@kernel.org> Cc: Liam R. Howlett <liam@infradead.org> Cc: Michal Hocko <mhocko@suse.com> Cc: Mike Rapoport <rppt@kernel.org> Cc: Suren Baghdasaryan <surenb@google.com> Cc: Vlastimil Babka <vbabka@kernel.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
144 lines
3.2 KiB
C
144 lines
3.2 KiB
C
// 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);
|
|
}
|