Blame view

mm/mprotect.c 13 KB
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
1
2
3
4
5
6
  /*
   *  mm/mprotect.c
   *
   *  (C) Copyright 1994 Linus Torvalds
   *  (C) Copyright 2002 Christoph Hellwig
   *
046c68842   Alan Cox   mm: update my add...
7
   *  Address space accounting code	<alan@lxorguk.ukuu.org.uk>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
8
9
10
11
12
   *  (C) Copyright 2002 Red Hat Inc, All Rights Reserved
   */
  
  #include <linux/mm.h>
  #include <linux/hugetlb.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
13
14
15
16
17
18
19
20
  #include <linux/shm.h>
  #include <linux/mman.h>
  #include <linux/fs.h>
  #include <linux/highmem.h>
  #include <linux/security.h>
  #include <linux/mempolicy.h>
  #include <linux/personality.h>
  #include <linux/syscalls.h>
0697212a4   Christoph Lameter   [PATCH] Swapless ...
21
22
  #include <linux/swap.h>
  #include <linux/swapops.h>
cddb8a5c1   Andrea Arcangeli   mmu-notifiers: core
23
  #include <linux/mmu_notifier.h>
64cdd548f   KOSAKI Motohiro   mm: cleanup: remo...
24
  #include <linux/migrate.h>
cdd6c482c   Ingo Molnar   perf: Do the big ...
25
  #include <linux/perf_event.h>
e8c24d3a2   Dave Hansen   x86/pkeys: Alloca...
26
  #include <linux/pkeys.h>
64a9a34e2   Mel Gorman   mm: numa: do not ...
27
  #include <linux/ksm.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
28
29
30
  #include <asm/uaccess.h>
  #include <asm/pgtable.h>
  #include <asm/cacheflush.h>
e8c24d3a2   Dave Hansen   x86/pkeys: Alloca...
31
  #include <asm/mmu_context.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
32
  #include <asm/tlbflush.h>
36f881883   Kirill A. Shutemov   mm: fix mprotect(...
33
  #include "internal.h"
1ad9f620c   Mel Gorman   mm: numa: recheck...
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
  /*
   * For a prot_numa update we only hold mmap_sem for read so there is a
   * potential race with faulting where a pmd was temporarily none. This
   * function checks for a transhuge pmd under the appropriate lock. It
   * returns a pte if it was successfully locked or NULL if it raced with
   * a transhuge insertion.
   */
  static pte_t *lock_pte_protection(struct vm_area_struct *vma, pmd_t *pmd,
  			unsigned long addr, int prot_numa, spinlock_t **ptl)
  {
  	pte_t *pte;
  	spinlock_t *pmdl;
  
  	/* !prot_numa is protected by mmap_sem held for write */
  	if (!prot_numa)
  		return pte_offset_map_lock(vma->vm_mm, pmd, addr, ptl);
  
  	pmdl = pmd_lock(vma->vm_mm, pmd);
  	if (unlikely(pmd_trans_huge(*pmd) || pmd_none(*pmd))) {
  		spin_unlock(pmdl);
  		return NULL;
  	}
  
  	pte = pte_offset_map_lock(vma->vm_mm, pmd, addr, ptl);
  	spin_unlock(pmdl);
  	return pte;
  }
4b10e7d56   Mel Gorman   mm: mempolicy: Im...
61
  static unsigned long change_pte_range(struct vm_area_struct *vma, pmd_t *pmd,
c1e6098b2   Peter Zijlstra   [PATCH] mm: optim...
62
  		unsigned long addr, unsigned long end, pgprot_t newprot,
0f19c1792   Mel Gorman   mm: numa: Do not ...
63
  		int dirty_accountable, int prot_numa)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
64
  {
4b10e7d56   Mel Gorman   mm: mempolicy: Im...
65
  	struct mm_struct *mm = vma->vm_mm;
0697212a4   Christoph Lameter   [PATCH] Swapless ...
66
  	pte_t *pte, oldpte;
705e87c0c   Hugh Dickins   [PATCH] mm: pte_o...
67
  	spinlock_t *ptl;
7da4d641c   Peter Zijlstra   mm: Count the num...
68
  	unsigned long pages = 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
69

1ad9f620c   Mel Gorman   mm: numa: recheck...
70
71
72
  	pte = lock_pte_protection(vma, pmd, addr, prot_numa, &ptl);
  	if (!pte)
  		return 0;
6606c3e0d   Zachary Amsden   [PATCH] paravirt:...
73
  	arch_enter_lazy_mmu_mode();
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
74
  	do {
0697212a4   Christoph Lameter   [PATCH] Swapless ...
75
76
  		oldpte = *pte;
  		if (pte_present(oldpte)) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
77
  			pte_t ptent;
b191f9b10   Mel Gorman   mm: numa: preserv...
78
  			bool preserve_write = prot_numa && pte_write(oldpte);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
79

e944fd67b   Mel Gorman   mm: numa: do not ...
80
81
82
83
84
85
86
87
88
89
  			/*
  			 * Avoid trapping faults against the zero or KSM
  			 * pages. See similar comment in change_huge_pmd.
  			 */
  			if (prot_numa) {
  				struct page *page;
  
  				page = vm_normal_page(vma, addr, oldpte);
  				if (!page || PageKsm(page))
  					continue;
10c1045f2   Mel Gorman   mm: numa: avoid u...
90
91
92
93
  
  				/* Avoid TLB flush if possible */
  				if (pte_protnone(oldpte))
  					continue;
e944fd67b   Mel Gorman   mm: numa: do not ...
94
  			}
8a0516ed8   Mel Gorman   mm: convert p[te|...
95
96
  			ptent = ptep_modify_prot_start(mm, addr, pte);
  			ptent = pte_modify(ptent, newprot);
b191f9b10   Mel Gorman   mm: numa: preserv...
97
98
  			if (preserve_write)
  				ptent = pte_mkwrite(ptent);
4b10e7d56   Mel Gorman   mm: mempolicy: Im...
99

8a0516ed8   Mel Gorman   mm: convert p[te|...
100
101
102
103
104
  			/* Avoid taking write faults for known dirty pages */
  			if (dirty_accountable && pte_dirty(ptent) &&
  					(pte_soft_dirty(ptent) ||
  					 !(vma->vm_flags & VM_SOFTDIRTY))) {
  				ptent = pte_mkwrite(ptent);
4b10e7d56   Mel Gorman   mm: mempolicy: Im...
105
  			}
8a0516ed8   Mel Gorman   mm: convert p[te|...
106
107
  			ptep_modify_prot_commit(mm, addr, pte, ptent);
  			pages++;
0661a3361   Kirill A. Shutemov   mm: remove rest u...
108
  		} else if (IS_ENABLED(CONFIG_MIGRATION)) {
0697212a4   Christoph Lameter   [PATCH] Swapless ...
109
110
111
  			swp_entry_t entry = pte_to_swp_entry(oldpte);
  
  			if (is_write_migration_entry(entry)) {
c3d16e165   Cyrill Gorcunov   mm: migration: do...
112
  				pte_t newpte;
0697212a4   Christoph Lameter   [PATCH] Swapless ...
113
114
115
116
117
  				/*
  				 * A protection check is difficult so
  				 * just be safe and disable write
  				 */
  				make_migration_entry_read(&entry);
c3d16e165   Cyrill Gorcunov   mm: migration: do...
118
119
120
121
  				newpte = swp_entry_to_pte(entry);
  				if (pte_swp_soft_dirty(oldpte))
  					newpte = pte_swp_mksoft_dirty(newpte);
  				set_pte_at(mm, addr, pte, newpte);
e920e14ca   Mel Gorman   mm: Do not flush ...
122
123
  
  				pages++;
0697212a4   Christoph Lameter   [PATCH] Swapless ...
124
  			}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
125
126
  		}
  	} while (pte++, addr += PAGE_SIZE, addr != end);
6606c3e0d   Zachary Amsden   [PATCH] paravirt:...
127
  	arch_leave_lazy_mmu_mode();
705e87c0c   Hugh Dickins   [PATCH] mm: pte_o...
128
  	pte_unmap_unlock(pte - 1, ptl);
7da4d641c   Peter Zijlstra   mm: Count the num...
129
130
  
  	return pages;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
131
  }
7d12efaea   Andrew Morton   mm/mprotect.c: co...
132
133
134
  static inline unsigned long change_pmd_range(struct vm_area_struct *vma,
  		pud_t *pud, unsigned long addr, unsigned long end,
  		pgprot_t newprot, int dirty_accountable, int prot_numa)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
135
136
  {
  	pmd_t *pmd;
a5338093b   Rik van Riel   mm: move mmu noti...
137
  	struct mm_struct *mm = vma->vm_mm;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
138
  	unsigned long next;
7da4d641c   Peter Zijlstra   mm: Count the num...
139
  	unsigned long pages = 0;
72403b4a0   Mel Gorman   mm: numa: return ...
140
  	unsigned long nr_huge_updates = 0;
a5338093b   Rik van Riel   mm: move mmu noti...
141
  	unsigned long mni_start = 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
142
143
144
  
  	pmd = pmd_offset(pud, addr);
  	do {
25cbbef19   Mel Gorman   mm: numa: Trap pm...
145
  		unsigned long this_pages;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
146
  		next = pmd_addr_end(addr, end);
5c7fb56e5   Dan Williams   mm, dax: dax-pmd ...
147
148
  		if (!pmd_trans_huge(*pmd) && !pmd_devmap(*pmd)
  				&& pmd_none_or_clear_bad(pmd))
88a9ab6e3   Rik van Riel   mm,numa: reorgani...
149
  			continue;
a5338093b   Rik van Riel   mm: move mmu noti...
150
151
152
153
154
155
  
  		/* invoke the mmu notifier if the pmd is populated */
  		if (!mni_start) {
  			mni_start = addr;
  			mmu_notifier_invalidate_range_start(mm, mni_start, end);
  		}
5c7fb56e5   Dan Williams   mm, dax: dax-pmd ...
156
  		if (pmd_trans_huge(*pmd) || pmd_devmap(*pmd)) {
6b9116a65   Kirill A. Shutemov   mm, dax: check fo...
157
  			if (next - addr != HPAGE_PMD_SIZE) {
78ddc5347   Kirill A. Shutemov   thp: rename split...
158
  				split_huge_pmd(vma, pmd, addr);
337d9abf1   Naoya Horiguchi   mm: thp: check pm...
159
  				if (pmd_trans_unstable(pmd))
6b9116a65   Kirill A. Shutemov   mm, dax: check fo...
160
161
  					continue;
  			} else {
f123d74ab   Mel Gorman   mm: Only flush TL...
162
  				int nr_ptes = change_huge_pmd(vma, pmd, addr,
e944fd67b   Mel Gorman   mm: numa: do not ...
163
  						newprot, prot_numa);
f123d74ab   Mel Gorman   mm: Only flush TL...
164
165
  
  				if (nr_ptes) {
72403b4a0   Mel Gorman   mm: numa: return ...
166
167
168
169
  					if (nr_ptes == HPAGE_PMD_NR) {
  						pages += HPAGE_PMD_NR;
  						nr_huge_updates++;
  					}
1ad9f620c   Mel Gorman   mm: numa: recheck...
170
171
  
  					/* huge pmd was handled */
f123d74ab   Mel Gorman   mm: Only flush TL...
172
173
  					continue;
  				}
7da4d641c   Peter Zijlstra   mm: Count the num...
174
  			}
88a9ab6e3   Rik van Riel   mm,numa: reorgani...
175
  			/* fall through, the trans huge pmd just split */
cd7548ab3   Johannes Weiner   thp: mprotect: tr...
176
  		}
25cbbef19   Mel Gorman   mm: numa: Trap pm...
177
  		this_pages = change_pte_range(vma, pmd, addr, next, newprot,
0f19c1792   Mel Gorman   mm: numa: Do not ...
178
  				 dirty_accountable, prot_numa);
25cbbef19   Mel Gorman   mm: numa: Trap pm...
179
  		pages += this_pages;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
180
  	} while (pmd++, addr = next, addr != end);
7da4d641c   Peter Zijlstra   mm: Count the num...
181

a5338093b   Rik van Riel   mm: move mmu noti...
182
183
  	if (mni_start)
  		mmu_notifier_invalidate_range_end(mm, mni_start, end);
72403b4a0   Mel Gorman   mm: numa: return ...
184
185
  	if (nr_huge_updates)
  		count_vm_numa_events(NUMA_HUGE_PTE_UPDATES, nr_huge_updates);
7da4d641c   Peter Zijlstra   mm: Count the num...
186
  	return pages;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
187
  }
7d12efaea   Andrew Morton   mm/mprotect.c: co...
188
189
190
  static inline unsigned long change_pud_range(struct vm_area_struct *vma,
  		pgd_t *pgd, unsigned long addr, unsigned long end,
  		pgprot_t newprot, int dirty_accountable, int prot_numa)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
191
192
193
  {
  	pud_t *pud;
  	unsigned long next;
7da4d641c   Peter Zijlstra   mm: Count the num...
194
  	unsigned long pages = 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
195
196
197
198
199
200
  
  	pud = pud_offset(pgd, addr);
  	do {
  		next = pud_addr_end(addr, end);
  		if (pud_none_or_clear_bad(pud))
  			continue;
7da4d641c   Peter Zijlstra   mm: Count the num...
201
  		pages += change_pmd_range(vma, pud, addr, next, newprot,
4b10e7d56   Mel Gorman   mm: mempolicy: Im...
202
  				 dirty_accountable, prot_numa);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
203
  	} while (pud++, addr = next, addr != end);
7da4d641c   Peter Zijlstra   mm: Count the num...
204
205
  
  	return pages;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
206
  }
7da4d641c   Peter Zijlstra   mm: Count the num...
207
  static unsigned long change_protection_range(struct vm_area_struct *vma,
c1e6098b2   Peter Zijlstra   [PATCH] mm: optim...
208
  		unsigned long addr, unsigned long end, pgprot_t newprot,
4b10e7d56   Mel Gorman   mm: mempolicy: Im...
209
  		int dirty_accountable, int prot_numa)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
210
211
212
213
214
  {
  	struct mm_struct *mm = vma->vm_mm;
  	pgd_t *pgd;
  	unsigned long next;
  	unsigned long start = addr;
7da4d641c   Peter Zijlstra   mm: Count the num...
215
  	unsigned long pages = 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
216
217
218
219
  
  	BUG_ON(addr >= end);
  	pgd = pgd_offset(mm, addr);
  	flush_cache_range(vma, addr, end);
208414059   Rik van Riel   mm: fix TLB flush...
220
  	set_tlb_flush_pending(mm);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
221
222
223
224
  	do {
  		next = pgd_addr_end(addr, end);
  		if (pgd_none_or_clear_bad(pgd))
  			continue;
7da4d641c   Peter Zijlstra   mm: Count the num...
225
  		pages += change_pud_range(vma, pgd, addr, next, newprot,
4b10e7d56   Mel Gorman   mm: mempolicy: Im...
226
  				 dirty_accountable, prot_numa);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
227
  	} while (pgd++, addr = next, addr != end);
7da4d641c   Peter Zijlstra   mm: Count the num...
228

1233d5882   Ingo Molnar   mm: Optimize the ...
229
230
231
  	/* Only flush the TLB if we actually modified any entries: */
  	if (pages)
  		flush_tlb_range(vma, start, end);
208414059   Rik van Riel   mm: fix TLB flush...
232
  	clear_tlb_flush_pending(mm);
7da4d641c   Peter Zijlstra   mm: Count the num...
233
234
235
236
237
238
  
  	return pages;
  }
  
  unsigned long change_protection(struct vm_area_struct *vma, unsigned long start,
  		       unsigned long end, pgprot_t newprot,
4b10e7d56   Mel Gorman   mm: mempolicy: Im...
239
  		       int dirty_accountable, int prot_numa)
7da4d641c   Peter Zijlstra   mm: Count the num...
240
  {
7da4d641c   Peter Zijlstra   mm: Count the num...
241
  	unsigned long pages;
7da4d641c   Peter Zijlstra   mm: Count the num...
242
243
244
  	if (is_vm_hugetlb_page(vma))
  		pages = hugetlb_change_protection(vma, start, end, newprot);
  	else
4b10e7d56   Mel Gorman   mm: mempolicy: Im...
245
  		pages = change_protection_range(vma, start, end, newprot, dirty_accountable, prot_numa);
7da4d641c   Peter Zijlstra   mm: Count the num...
246
247
  
  	return pages;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
248
  }
b6a2fea39   Ollie Wild   mm: variable leng...
249
  int
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
250
251
252
253
254
255
256
  mprotect_fixup(struct vm_area_struct *vma, struct vm_area_struct **pprev,
  	unsigned long start, unsigned long end, unsigned long newflags)
  {
  	struct mm_struct *mm = vma->vm_mm;
  	unsigned long oldflags = vma->vm_flags;
  	long nrpages = (end - start) >> PAGE_SHIFT;
  	unsigned long charged = 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
257
258
  	pgoff_t pgoff;
  	int error;
c1e6098b2   Peter Zijlstra   [PATCH] mm: optim...
259
  	int dirty_accountable = 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
260
261
262
263
264
265
266
267
268
  
  	if (newflags == oldflags) {
  		*pprev = vma;
  		return 0;
  	}
  
  	/*
  	 * If we make a private mapping writable we increase our commit;
  	 * but (without finer accounting) cannot reduce our commit if we
5a6fe1259   Mel Gorman   Do not account fo...
269
270
  	 * make it unwritable again. hugetlb mapping were accounted for
  	 * even if read-only so there is no need to account for them here
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
271
272
  	 */
  	if (newflags & VM_WRITE) {
846383359   Konstantin Khlebnikov   mm: rework virtua...
273
274
275
276
  		/* Check space limits when area turns into data. */
  		if (!may_expand_vm(mm, newflags, nrpages) &&
  				may_expand_vm(mm, oldflags, nrpages))
  			return -ENOMEM;
5a6fe1259   Mel Gorman   Do not account fo...
277
  		if (!(oldflags & (VM_ACCOUNT|VM_WRITE|VM_HUGETLB|
cdfd4325c   Andy Whitcroft   mm: record MAP_NO...
278
  						VM_SHARED|VM_NORESERVE))) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
279
  			charged = nrpages;
191c54244   Al Viro   mm: collapse secu...
280
  			if (security_vm_enough_memory_mm(mm, charged))
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
281
282
283
284
  				return -ENOMEM;
  			newflags |= VM_ACCOUNT;
  		}
  	}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
285
286
287
288
289
  	/*
  	 * First try to merge with previous and/or next vma.
  	 */
  	pgoff = vma->vm_pgoff + ((start - vma->vm_start) >> PAGE_SHIFT);
  	*pprev = vma_merge(mm, *pprev, start, end, newflags,
19a809afe   Andrea Arcangeli   userfaultfd: teac...
290
291
  			   vma->anon_vma, vma->vm_file, pgoff, vma_policy(vma),
  			   vma->vm_userfaultfd_ctx);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
292
293
  	if (*pprev) {
  		vma = *pprev;
e86f15ee6   Andrea Arcangeli   mm: vma_merge: fi...
294
  		VM_WARN_ON((vma->vm_flags ^ newflags) & ~VM_SOFTDIRTY);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
  		goto success;
  	}
  
  	*pprev = vma;
  
  	if (start != vma->vm_start) {
  		error = split_vma(mm, vma, start, 1);
  		if (error)
  			goto fail;
  	}
  
  	if (end != vma->vm_end) {
  		error = split_vma(mm, vma, end, 0);
  		if (error)
  			goto fail;
  	}
  
  success:
  	/*
  	 * vm_flags and vm_page_prot are protected by the mmap_sem
  	 * held in write mode.
  	 */
  	vma->vm_flags = newflags;
6d2329f88   Andrea Arcangeli   mm: vm_page_prot:...
318
  	dirty_accountable = vma_wants_writenotify(vma, vma->vm_page_prot);
64e455079   Peter Feiner   mm: softdirty: en...
319
  	vma_set_page_prot(vma);
d08b3851d   Peter Zijlstra   [PATCH] mm: track...
320

7d12efaea   Andrew Morton   mm/mprotect.c: co...
321
322
  	change_protection(vma, start, end, vma->vm_page_prot,
  			  dirty_accountable, 0);
7da4d641c   Peter Zijlstra   mm: Count the num...
323

36f881883   Kirill A. Shutemov   mm: fix mprotect(...
324
325
326
327
328
329
330
331
  	/*
  	 * Private VM_LOCKED VMA becoming writable: trigger COW to avoid major
  	 * fault on access.
  	 */
  	if ((oldflags & (VM_WRITE | VM_SHARED | VM_LOCKED)) == VM_LOCKED &&
  			(newflags & VM_WRITE)) {
  		populate_vma_page_range(vma, start, end, NULL);
  	}
846383359   Konstantin Khlebnikov   mm: rework virtua...
332
333
  	vm_stat_account(mm, oldflags, -nrpages);
  	vm_stat_account(mm, newflags, nrpages);
63bfd7384   Pekka Enberg   perf_events: Fix ...
334
  	perf_event_mmap(vma);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
335
336
337
338
339
340
  	return 0;
  
  fail:
  	vm_unacct_memory(charged);
  	return error;
  }
7d06d9c9b   Dave Hansen   mm: Implement new...
341
342
343
344
345
  /*
   * pkey==-1 when doing a legacy mprotect()
   */
  static int do_mprotect_pkey(unsigned long start, size_t len,
  		unsigned long prot, int pkey)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
346
  {
62b5f7d01   Dave Hansen   mm/core, x86/mm/p...
347
  	unsigned long nstart, end, tmp, reqprot;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
348
349
350
  	struct vm_area_struct *vma, *prev;
  	int error = -EINVAL;
  	const int grows = prot & (PROT_GROWSDOWN|PROT_GROWSUP);
f138556da   Piotr Kwapulinski   mm/mprotect.c: do...
351
352
  	const bool rier = (current->personality & READ_IMPLIES_EXEC) &&
  				(prot & PROT_READ);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
353
354
355
356
357
358
359
360
361
362
363
364
  	prot &= ~(PROT_GROWSDOWN|PROT_GROWSUP);
  	if (grows == (PROT_GROWSDOWN|PROT_GROWSUP)) /* can't be both */
  		return -EINVAL;
  
  	if (start & ~PAGE_MASK)
  		return -EINVAL;
  	if (!len)
  		return 0;
  	len = PAGE_ALIGN(len);
  	end = start + len;
  	if (end <= start)
  		return -ENOMEM;
b845f313d   Dave Kleikamp   mm: Allow archite...
365
  	if (!arch_validate_prot(prot))
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
366
367
368
  		return -EINVAL;
  
  	reqprot = prot;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
369

dc0ef0df7   Michal Hocko   mm: make mmap_sem...
370
371
  	if (down_write_killable(&current->mm->mmap_sem))
  		return -EINTR;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
372

e8c24d3a2   Dave Hansen   x86/pkeys: Alloca...
373
374
375
376
377
378
379
  	/*
  	 * If userspace did not allocate the pkey, do not let
  	 * them use it here.
  	 */
  	error = -EINVAL;
  	if ((pkey != -1) && !mm_pkey_is_allocated(current->mm, pkey))
  		goto out;
097d59106   Linus Torvalds   vm: avoid using f...
380
  	vma = find_vma(current->mm, start);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
381
382
383
  	error = -ENOMEM;
  	if (!vma)
  		goto out;
097d59106   Linus Torvalds   vm: avoid using f...
384
  	prev = vma->vm_prev;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
385
386
387
388
389
390
391
  	if (unlikely(grows & PROT_GROWSDOWN)) {
  		if (vma->vm_start >= end)
  			goto out;
  		start = vma->vm_start;
  		error = -EINVAL;
  		if (!(vma->vm_flags & VM_GROWSDOWN))
  			goto out;
7d12efaea   Andrew Morton   mm/mprotect.c: co...
392
  	} else {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
393
394
395
396
397
398
399
400
401
402
403
404
405
  		if (vma->vm_start > start)
  			goto out;
  		if (unlikely(grows & PROT_GROWSUP)) {
  			end = vma->vm_end;
  			error = -EINVAL;
  			if (!(vma->vm_flags & VM_GROWSUP))
  				goto out;
  		}
  	}
  	if (start > vma->vm_start)
  		prev = vma;
  
  	for (nstart = start ; ; ) {
a8502b67d   Dave Hansen   x86/pkeys: Make m...
406
  		unsigned long mask_off_old_flags;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
407
  		unsigned long newflags;
7d06d9c9b   Dave Hansen   mm: Implement new...
408
  		int new_vma_pkey;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
409

7d12efaea   Andrew Morton   mm/mprotect.c: co...
410
  		/* Here we know that vma->vm_start <= nstart < vma->vm_end. */
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
411

f138556da   Piotr Kwapulinski   mm/mprotect.c: do...
412
413
414
  		/* Does the application expect PROT_READ to imply PROT_EXEC */
  		if (rier && (vma->vm_flags & VM_MAYEXEC))
  			prot |= PROT_EXEC;
a8502b67d   Dave Hansen   x86/pkeys: Make m...
415
416
417
418
419
420
421
  		/*
  		 * Each mprotect() call explicitly passes r/w/x permissions.
  		 * If a permission is not passed to mprotect(), it must be
  		 * cleared from the VMA.
  		 */
  		mask_off_old_flags = VM_READ | VM_WRITE | VM_EXEC |
  					ARCH_VM_PKEY_FLAGS;
7d06d9c9b   Dave Hansen   mm: Implement new...
422
423
  		new_vma_pkey = arch_override_mprotect_pkey(vma, prot, pkey);
  		newflags = calc_vm_prot_bits(prot, new_vma_pkey);
a8502b67d   Dave Hansen   x86/pkeys: Make m...
424
  		newflags |= (vma->vm_flags & ~mask_off_old_flags);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
425

7e2cff42c   Paolo 'Blaisorblade' Giarrusso   [PATCH] mm: add a...
426
427
  		/* newflags >> 4 shift VM_MAY% in place of VM_% */
  		if ((newflags & ~(newflags >> 4)) & (VM_READ | VM_WRITE | VM_EXEC)) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
  			error = -EACCES;
  			goto out;
  		}
  
  		error = security_file_mprotect(vma, reqprot, prot);
  		if (error)
  			goto out;
  
  		tmp = vma->vm_end;
  		if (tmp > end)
  			tmp = end;
  		error = mprotect_fixup(vma, &prev, nstart, tmp, newflags);
  		if (error)
  			goto out;
  		nstart = tmp;
  
  		if (nstart < prev->vm_end)
  			nstart = prev->vm_end;
  		if (nstart >= end)
  			goto out;
  
  		vma = prev->vm_next;
  		if (!vma || vma->vm_start != nstart) {
  			error = -ENOMEM;
  			goto out;
  		}
f138556da   Piotr Kwapulinski   mm/mprotect.c: do...
454
  		prot = reqprot;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
455
456
457
458
459
  	}
  out:
  	up_write(&current->mm->mmap_sem);
  	return error;
  }
7d06d9c9b   Dave Hansen   mm: Implement new...
460
461
462
463
464
465
466
467
468
469
470
471
  
  SYSCALL_DEFINE3(mprotect, unsigned long, start, size_t, len,
  		unsigned long, prot)
  {
  	return do_mprotect_pkey(start, len, prot, -1);
  }
  
  SYSCALL_DEFINE4(pkey_mprotect, unsigned long, start, size_t, len,
  		unsigned long, prot, int, pkey)
  {
  	return do_mprotect_pkey(start, len, prot, pkey);
  }
e8c24d3a2   Dave Hansen   x86/pkeys: Alloca...
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
  
  SYSCALL_DEFINE2(pkey_alloc, unsigned long, flags, unsigned long, init_val)
  {
  	int pkey;
  	int ret;
  
  	/* No flags supported yet. */
  	if (flags)
  		return -EINVAL;
  	/* check for unsupported init values */
  	if (init_val & ~PKEY_ACCESS_MASK)
  		return -EINVAL;
  
  	down_write(&current->mm->mmap_sem);
  	pkey = mm_pkey_alloc(current->mm);
  
  	ret = -ENOSPC;
  	if (pkey == -1)
  		goto out;
  
  	ret = arch_set_user_pkey_access(current, pkey, init_val);
  	if (ret) {
  		mm_pkey_free(current->mm, pkey);
  		goto out;
  	}
  	ret = pkey;
  out:
  	up_write(&current->mm->mmap_sem);
  	return ret;
  }
  
  SYSCALL_DEFINE1(pkey_free, int, pkey)
  {
  	int ret;
  
  	down_write(&current->mm->mmap_sem);
  	ret = mm_pkey_free(current->mm, pkey);
  	up_write(&current->mm->mmap_sem);
  
  	/*
  	 * We could provie warnings or errors if any VMA still
  	 * has the pkey set here.
  	 */
  	return ret;
  }