1
2
3
4
5
6
7
8
9
10
11#include <linux/mm.h>
12#include <linux/hugetlb.h>
13#include <linux/slab.h>
14#include <linux/shm.h>
15#include <linux/mman.h>
16#include <linux/fs.h>
17#include <linux/highmem.h>
18#include <linux/security.h>
19#include <linux/mempolicy.h>
20#include <linux/personality.h>
21
22#include <asm/uaccess.h>
23#include <asm/pgtable.h>
24#include <asm/pgalloc.h>
25#include <asm/cacheflush.h>
26#include <asm/tlbflush.h>
27
28static inline void
29change_pte_range(pmd_t *pmd, unsigned long address,
30 unsigned long size, pgprot_t newprot)
31{
32 pte_t * pte;
33 unsigned long end;
34
35 if (pmd_none(*pmd))
36 return;
37 if (pmd_bad(*pmd)) {
38 pmd_ERROR(*pmd);
39 pmd_clear(pmd);
40 return;
41 }
42 pte = pte_offset_map(pmd, address);
43 address &= ~PMD_MASK;
44 end = address + size;
45 if (end > PMD_SIZE)
46 end = PMD_SIZE;
47 do {
48 if (pte_present(*pte)) {
49 pte_t entry;
50
51
52
53
54
55 entry = pte_modify(ptep_get_and_clear(pte), newprot);
56 set_pte(pte, entry);
57 lazy_mmu_prot_update(entry);
58 }
59 address += PAGE_SIZE;
60 pte++;
61 } while (address && (address < end));
62 pte_unmap(pte - 1);
63}
64
65static inline void
66change_pmd_range(pgd_t *pgd, unsigned long address,
67 unsigned long size, pgprot_t newprot)
68{
69 pmd_t * pmd;
70 unsigned long end;
71
72 if (pgd_none(*pgd))
73 return;
74 if (pgd_bad(*pgd)) {
75 pgd_ERROR(*pgd);
76 pgd_clear(pgd);
77 return;
78 }
79 pmd = pmd_offset(pgd, address);
80 address &= ~PGDIR_MASK;
81 end = address + size;
82 if (end > PGDIR_SIZE)
83 end = PGDIR_SIZE;
84 do {
85 change_pte_range(pmd, address, end - address, newprot);
86 address = (address + PMD_SIZE) & PMD_MASK;
87 pmd++;
88 } while (address && (address < end));
89}
90
91static void
92change_protection(struct vm_area_struct *vma, unsigned long start,
93 unsigned long end, pgprot_t newprot)
94{
95 pgd_t *dir;
96 unsigned long beg = start;
97
98 dir = pgd_offset(current->mm, start);
99 flush_cache_range(vma, beg, end);
100 if (start >= end)
101 BUG();
102 spin_lock(¤t->mm->page_table_lock);
103 do {
104 change_pmd_range(dir, start, end - start, newprot);
105 start = (start + PGDIR_SIZE) & PGDIR_MASK;
106 dir++;
107 } while (start && (start < end));
108 flush_tlb_range(vma, beg, end);
109 spin_unlock(¤t->mm->page_table_lock);
110 return;
111}
112
113static int
114mprotect_fixup(struct vm_area_struct *vma, struct vm_area_struct **pprev,
115 unsigned long start, unsigned long end, unsigned int newflags)
116{
117 struct mm_struct * mm = vma->vm_mm;
118 unsigned long charged = 0, old_end = vma->vm_end;
119 pgprot_t newprot;
120 unsigned int oldflags;
121 pgoff_t pgoff;
122 int error;
123
124 if (newflags == vma->vm_flags) {
125 *pprev = vma;
126 return 0;
127 }
128
129
130
131
132
133
134
135
136
137 if (newflags & VM_WRITE) {
138 if (!(vma->vm_flags & (VM_ACCOUNT|VM_WRITE|VM_SHARED|VM_HUGETLB))) {
139 charged = (end - start) >> PAGE_SHIFT;
140 if (security_vm_enough_memory(charged))
141 return -ENOMEM;
142 newflags |= VM_ACCOUNT;
143 }
144 }
145
146 newprot = protection_map[newflags & 0xf];
147
148
149
150
151 pgoff = vma->vm_pgoff + ((start - vma->vm_start) >> PAGE_SHIFT);
152 *pprev = vma_merge(mm, *pprev, start, end, newflags,
153 vma->anon_vma, vma->vm_file, pgoff, vma_policy(vma));
154 if (*pprev) {
155 vma = *pprev;
156 goto success;
157 }
158
159 if (start != vma->vm_start) {
160 error = split_vma(mm, vma, start, 1);
161 if (error)
162 goto fail;
163 }
164
165
166
167
168 *pprev = vma;
169
170 if (end != vma->vm_end) {
171 error = split_vma(mm, vma, end, 0);
172 if (error)
173 goto fail;
174 }
175
176success:
177
178
179
180
181 vm_stat_unaccount(vma);
182 oldflags = vma->vm_flags;
183 vma->vm_flags = newflags;
184 vma->vm_page_prot = newprot;
185 if (oldflags & VM_EXEC)
186 arch_remove_exec_range(current->mm, old_end);
187 change_protection(vma, start, end, newprot);
188 vm_stat_account(vma);
189 return 0;
190
191fail:
192 vm_unacct_memory(charged);
193 return error;
194}
195
196asmlinkage long
197sys_mprotect(unsigned long start, size_t len, unsigned long prot)
198{
199 unsigned long vm_flags, nstart, end, tmp;
200 struct vm_area_struct *vma, *prev;
201 int error = -EINVAL;
202 const int grows = prot & (PROT_GROWSDOWN|PROT_GROWSUP);
203 prot &= ~(PROT_GROWSDOWN|PROT_GROWSUP);
204 if (grows == (PROT_GROWSDOWN|PROT_GROWSUP))
205 return -EINVAL;
206
207 if (start & ~PAGE_MASK)
208 return -EINVAL;
209 len = PAGE_ALIGN(len);
210 end = start + len;
211 if (end < start)
212 return -ENOMEM;
213 if (prot & ~(PROT_READ | PROT_WRITE | PROT_EXEC | PROT_SEM))
214 return -EINVAL;
215 if (end == start)
216 return 0;
217
218
219
220 if (unlikely((prot & PROT_READ) &&
221 (current->personality & READ_IMPLIES_EXEC)))
222 prot |= PROT_EXEC;
223
224 vm_flags = calc_vm_prot_bits(prot);
225
226 down_write(¤t->mm->mmap_sem);
227
228 vma = find_vma_prev(current->mm, start, &prev);
229 error = -ENOMEM;
230 if (!vma)
231 goto out;
232 if (unlikely(grows & PROT_GROWSDOWN)) {
233 if (vma->vm_start >= end)
234 goto out;
235 start = vma->vm_start;
236 error = -EINVAL;
237 if (!(vma->vm_flags & VM_GROWSDOWN))
238 goto out;
239 }
240 else {
241 if (vma->vm_start > start)
242 goto out;
243 if (unlikely(grows & PROT_GROWSUP)) {
244 end = vma->vm_end;
245 error = -EINVAL;
246 if (!(vma->vm_flags & VM_GROWSUP))
247 goto out;
248 }
249 }
250 if (start > vma->vm_start)
251 prev = vma;
252
253 for (nstart = start ; ; ) {
254 unsigned int newflags;
255
256
257
258 if (is_vm_hugetlb_page(vma)) {
259 error = -EACCES;
260 goto out;
261 }
262
263 newflags = vm_flags | (vma->vm_flags & ~(VM_READ | VM_WRITE | VM_EXEC));
264
265 if ((newflags & ~(newflags >> 4)) & 0xf) {
266 error = -EACCES;
267 goto out;
268 }
269
270 error = security_file_mprotect(vma, prot);
271 if (error)
272 goto out;
273
274 tmp = vma->vm_end;
275 if (tmp > end)
276 tmp = end;
277 error = mprotect_fixup(vma, &prev, nstart, tmp, newflags);
278 if (error)
279 goto out;
280 nstart = tmp;
281
282 if (nstart < prev->vm_end)
283 nstart = prev->vm_end;
284 if (nstart >= end)
285 goto out;
286
287 vma = prev->vm_next;
288 if (!vma || vma->vm_start != nstart) {
289 error = -ENOMEM;
290 goto out;
291 }
292 }
293out:
294 up_write(¤t->mm->mmap_sem);
295 return error;
296}
297