1
2
3
4
5
6#include <linux/stat.h>
7#include <linux/sched.h>
8#include <linux/kernel.h>
9#include <linux/mm.h>
10#include <linux/smp.h>
11#include <linux/smp_lock.h>
12#include <linux/shm.h>
13#include <linux/errno.h>
14#include <linux/mman.h>
15#include <linux/string.h>
16#include <linux/slab.h>
17
18#include <asm/uaccess.h>
19#include <asm/system.h>
20#include <asm/pgtable.h>
21
22static inline void change_pte_range(pmd_t * pmd, unsigned long address,
23 unsigned long size, pgprot_t newprot)
24{
25 pte_t * pte;
26 unsigned long end;
27
28 if (pmd_none(*pmd))
29 return;
30 if (pmd_bad(*pmd)) {
31 printk("change_pte_range: bad pmd (%08lx)\n", pmd_val(*pmd));
32 pmd_clear(pmd);
33 return;
34 }
35 pte = pte_offset(pmd, address);
36 address &= ~PMD_MASK;
37 end = address + size;
38 if (end > PMD_SIZE)
39 end = PMD_SIZE;
40 do {
41 pte_t entry = *pte;
42 if (pte_present(entry))
43 set_pte(pte, pte_modify(entry, newprot));
44 address += PAGE_SIZE;
45 pte++;
46 } while (address < end);
47}
48
49static inline void change_pmd_range(pgd_t * pgd, unsigned long address,
50 unsigned long size, pgprot_t newprot)
51{
52 pmd_t * pmd;
53 unsigned long end;
54
55 if (pgd_none(*pgd))
56 return;
57 if (pgd_bad(*pgd)) {
58 printk("change_pmd_range: bad pgd (%08lx)\n", pgd_val(*pgd));
59 pgd_clear(pgd);
60 return;
61 }
62 pmd = pmd_offset(pgd, address);
63 address &= ~PGDIR_MASK;
64 end = address + size;
65 if (end > PGDIR_SIZE)
66 end = PGDIR_SIZE;
67 do {
68 change_pte_range(pmd, address, end - address, newprot);
69 address = (address + PMD_SIZE) & PMD_MASK;
70 pmd++;
71 } while (address < end);
72}
73
74static void change_protection(unsigned long start, unsigned long end, pgprot_t newprot)
75{
76 pgd_t *dir;
77 unsigned long beg = start;
78
79 dir = pgd_offset(current->mm, start);
80 flush_cache_range(current->mm, beg, end);
81 while (start < end) {
82 change_pmd_range(dir, start, end - start, newprot);
83 start = (start + PGDIR_SIZE) & PGDIR_MASK;
84 dir++;
85 }
86 flush_tlb_range(current->mm, beg, end);
87 return;
88}
89
90static inline int mprotect_fixup_all(struct vm_area_struct * vma,
91 int newflags, pgprot_t prot)
92{
93 vma->vm_flags = newflags;
94 vma->vm_page_prot = prot;
95 return 0;
96}
97
98static inline int mprotect_fixup_start(struct vm_area_struct * vma,
99 unsigned long end,
100 int newflags, pgprot_t prot)
101{
102 struct vm_area_struct * n;
103
104 n = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);
105 if (!n)
106 return -ENOMEM;
107 *n = *vma;
108 vma->vm_start = end;
109 n->vm_end = end;
110 vma->vm_offset += vma->vm_start - n->vm_start;
111 n->vm_flags = newflags;
112 n->vm_page_prot = prot;
113 n->vm_dentry = dget(n->vm_dentry);
114 if (n->vm_ops && n->vm_ops->open)
115 n->vm_ops->open(n);
116 insert_vm_struct(current->mm, n);
117 return 0;
118}
119
120static inline int mprotect_fixup_end(struct vm_area_struct * vma,
121 unsigned long start,
122 int newflags, pgprot_t prot)
123{
124 struct vm_area_struct * n;
125
126 n = kmem_cache_alloc(vm_area_cachep, GFP_KERNEL);
127 if (!n)
128 return -ENOMEM;
129 *n = *vma;
130 vma->vm_end = start;
131 n->vm_start = start;
132 n->vm_offset += n->vm_start - vma->vm_start;
133 n->vm_flags = newflags;
134 n->vm_page_prot = prot;
135 n->vm_dentry = dget(n->vm_dentry);
136 if (n->vm_ops && n->vm_ops->open)
137 n->vm_ops->open(n);
138 insert_vm_struct(current->mm, n);
139 return 0;
140}
141
142static inline int mprotect_fixup_middle(struct vm_area_struct * vma,
143 unsigned long start, unsigned long end,
144 int newflags, pgprot_t prot)
145{
146 struct vm_area_struct * left, * right;
147
148 left = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);
149 if (!left)
150 return -ENOMEM;
151 right = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);
152 if (!right) {
153 kmem_cache_free(vm_area_cachep, left);
154 return -ENOMEM;
155 }
156 *left = *vma;
157 *right = *vma;
158 left->vm_end = start;
159 vma->vm_start = start;
160 vma->vm_end = end;
161 right->vm_start = end;
162 vma->vm_offset += vma->vm_start - left->vm_start;
163 right->vm_offset += right->vm_start - left->vm_start;
164 vma->vm_flags = newflags;
165 vma->vm_page_prot = prot;
166 if (vma->vm_dentry)
167 vma->vm_dentry->d_count += 2;
168 if (vma->vm_ops && vma->vm_ops->open) {
169 vma->vm_ops->open(left);
170 vma->vm_ops->open(right);
171 }
172 insert_vm_struct(current->mm, left);
173 insert_vm_struct(current->mm, right);
174 return 0;
175}
176
177static int mprotect_fixup(struct vm_area_struct * vma,
178 unsigned long start, unsigned long end, unsigned int newflags)
179{
180 pgprot_t newprot;
181 int error;
182
183 if (newflags == vma->vm_flags)
184 return 0;
185 newprot = protection_map[newflags & 0xf];
186 if (start == vma->vm_start)
187 if (end == vma->vm_end)
188 error = mprotect_fixup_all(vma, newflags, newprot);
189 else
190 error = mprotect_fixup_start(vma, end, newflags, newprot);
191 else if (end == vma->vm_end)
192 error = mprotect_fixup_end(vma, start, newflags, newprot);
193 else
194 error = mprotect_fixup_middle(vma, start, end, newflags, newprot);
195
196 if (error)
197 return error;
198
199 change_protection(start, end, newprot);
200 return 0;
201}
202
203asmlinkage int sys_mprotect(unsigned long start, size_t len, unsigned long prot)
204{
205 unsigned long nstart, end, tmp;
206 struct vm_area_struct * vma, * next;
207 int error = -EINVAL;
208
209 lock_kernel();
210 if (start & ~PAGE_MASK)
211 goto out;
212 len = (len + ~PAGE_MASK) & PAGE_MASK;
213 end = start + len;
214 if (end < start)
215 goto out;
216 if (prot & ~(PROT_READ | PROT_WRITE | PROT_EXEC))
217 goto out;
218 error = 0;
219 if (end == start)
220 goto out;
221 vma = find_vma(current->mm, start);
222 error = -EFAULT;
223 if (!vma || vma->vm_start > start)
224 goto out;
225
226 for (nstart = start ; ; ) {
227 unsigned int newflags;
228
229
230
231 newflags = prot | (vma->vm_flags & ~(PROT_READ | PROT_WRITE | PROT_EXEC));
232 if ((newflags & ~(newflags >> 4)) & 0xf) {
233 error = -EACCES;
234 break;
235 }
236
237 if (vma->vm_end >= end) {
238 error = mprotect_fixup(vma, nstart, end, newflags);
239 break;
240 }
241
242 tmp = vma->vm_end;
243 next = vma->vm_next;
244 error = mprotect_fixup(vma, nstart, tmp, newflags);
245 if (error)
246 break;
247 nstart = tmp;
248 vma = next;
249 if (!vma || vma->vm_start != nstart) {
250 error = -EFAULT;
251 break;
252 }
253 }
254 merge_segments(current->mm, start, end);
255out:
256 unlock_kernel();
257 return error;
258}
259