xref: /linux/mm/mseal.c (revision 2f0f6b0773be0a1ec475097ae54848eea42adc7d)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  *  Implement mseal() syscall.
4  *
5  *  Copyright (c) 2023,2024 Google, Inc.
6  *
7  *  Author: Jeff Xu <jeffxu@chromium.org>
8  */
9 
10 #include <linux/mempolicy.h>
11 #include <linux/minmax.h>
12 #include <linux/mman.h>
13 #include <linux/mm.h>
14 #include <linux/mm_inline.h>
15 #include <linux/syscalls.h>
16 #include <linux/sched.h>
17 #include "internal.h"
18 
19 static bool range_contains_unmapped(unsigned long start, unsigned long end)
20 {
21 	VMA_ITERATOR(vmi, current->mm, start);
22 	unsigned long prev_end = start;
23 	struct vm_area_struct *vma;
24 
25 	for_each_vma_range(vmi, vma, end) {
26 		if (vma->vm_start > prev_end)
27 			return true;
28 
29 		prev_end = vma->vm_end;
30 	}
31 
32 	return prev_end < end;
33 }
34 
35 static int __mseal_range(unsigned long start, unsigned long end)
36 {
37 	VMA_ITERATOR(vmi, current->mm, start);
38 	struct vm_area_struct *vma, *prev;
39 
40 	/* We know there are no gaps so this will be non-NULL. */
41 	vma = vma_iter_load(&vmi);
42 	prev = vma_prev(&vmi);
43 	if (start > vma->vm_start)
44 		prev = vma;
45 
46 	for_each_vma_range(vmi, vma, end) {
47 		const unsigned long curr_start = max(vma->vm_start, start);
48 		const unsigned long curr_end = min(vma->vm_end, end);
49 
50 		if (!vma_test(vma, VMA_SEALED_BIT)) {
51 			vma_flags_t vma_flags = vma->flags;
52 
53 			vma_flags_set(&vma_flags, VMA_SEALED_BIT);
54 
55 			vma = vma_modify_flags(&vmi, prev, vma, curr_start,
56 					       curr_end, &vma_flags);
57 			if (IS_ERR(vma))
58 				return PTR_ERR(vma);
59 			vma_start_write(vma);
60 			vma_set_flags(vma, VMA_SEALED_BIT);
61 		}
62 
63 		prev = vma;
64 	}
65 
66 	return 0;
67 }
68 
69 static int mseal_range(unsigned long start, unsigned long end)
70 {
71 	int err;
72 
73 	err = mmap_write_lock_killable(current->mm);
74 	if (err)
75 		return err;
76 	if (range_contains_unmapped(start, end))
77 		err = -ENOMEM;
78 	else
79 		err = __mseal_range(start, end);
80 	mmap_write_unlock(current->mm);
81 	return err;
82 }
83 
84 /**
85  * mseal_mmap_page_zero() - If the MMAP_PAGE_ZERO personality is set, mseal()
86  * the page mapped at address zero.
87  */
88 void mseal_mmap_page_zero(void)
89 {
90 	int err;
91 
92 	if (WARN_ON_ONCE(!(current->personality & MMAP_PAGE_ZERO)))
93 		return;
94 
95 	err = mseal_range(0, PAGE_SIZE);
96 	if (err)
97 		pr_warn_ratelimited("pid=%d, couldn't seal address 0, ret=%d.\n",
98 				    task_pid_nr(current), err);
99 }
100 
101 /*
102  * Seal VMAs in the specified input range to prevent an attacker replacing what
103  * is mapped in the range with something else.
104  *
105  * Disallows:
106  * - VMA unmapping, remapping or shrinking.
107  * - Overwriting the VMA with another one via mmap(), mremap() or similar.
108  * - Alteration of properties via mprotect()/pkey_mprotect().
109  * - Destructive madvise() behaviours (like MADV_DONTNEED) on anonymous read-only
110  *   ranges.
111  *
112  * Since unmapped ranges can be mapped at any time, the input range must span
113  * mapped ranges only.
114  *
115  * The flags parameter is currently reserved.
116  */
117 SYSCALL_DEFINE3(mseal, unsigned long, start, size_t, len, unsigned long, flags)
118 {
119 	size_t len_aligned;
120 	unsigned long end;
121 
122 	/* Verify flags not set. */
123 	if (flags)
124 		return -EINVAL;
125 
126 	start = untagged_addr(start);
127 	if (!PAGE_ALIGNED(start))
128 		return -EINVAL;
129 
130 	len_aligned = PAGE_ALIGN(len);
131 	/* Check to see whether len was rounded up from small -ve to zero. */
132 	if (len && !len_aligned)
133 		return -EINVAL;
134 
135 	end = start + len_aligned;
136 	if (end < start)
137 		return -EINVAL;
138 
139 	if (end == start)
140 		return 0;
141 
142 	return mseal_range(start, end);
143 }
144