kern_pax.c revision 1.14 1 /* $NetBSD: kern_pax.c,v 1.14 2007/02/21 23:00:04 thorpej Exp $ */
2
3 /*-
4 * Copyright (c) 2006 Elad Efrat <elad (at) NetBSD.org>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30 #include "opt_pax.h"
31
32 #include <sys/param.h>
33 #include <sys/proc.h>
34 #include <sys/exec_elf.h>
35 #include <sys/pax.h>
36 #include <sys/sysctl.h>
37 #include <sys/malloc.h>
38 #include <sys/fileassoc.h>
39 #include <sys/syslog.h>
40 #include <sys/vnode.h>
41 #include <sys/queue.h>
42 #include <sys/kauth.h>
43
44 #ifdef PAX_MPROTECT
45 static int pax_mprotect_enabled = 1;
46 static int pax_mprotect_global = PAX_MPROTECT;
47
48 specificdata_key_t pax_mprotect_key;
49 #endif
50
51 #ifdef PAX_SEGVGUARD
52 #ifndef PAX_SEGVGUARD_EXPIRY
53 #define PAX_SEGVGUARD_EXPIRY (2 * 60)
54 #endif
55
56 #ifndef PAX_SEGVGUARD_SUSPENSION
57 #define PAX_SEGVGUARD_SUSPENSION (10 * 60)
58 #endif
59
60 #ifndef PAX_SEGVGUARD_MAXCRASHES
61 #define PAX_SEGVGUARD_MAXCRASHES 5
62 #endif
63
64 static int pax_segvguard_enabled = 1;
65 static int pax_segvguard_global = PAX_SEGVGUARD;
66 static int pax_segvguard_expiry = PAX_SEGVGUARD_EXPIRY;
67 static int pax_segvguard_suspension = PAX_SEGVGUARD_SUSPENSION;
68 static int pax_segvguard_maxcrashes = PAX_SEGVGUARD_MAXCRASHES;
69
70 static fileassoc_t segvguard_id;
71 specificdata_key_t pax_segvguard_key;
72
73 struct pax_segvguard_uid_entry {
74 uid_t sue_uid;
75 size_t sue_ncrashes;
76 time_t sue_expiry;
77 time_t sue_suspended;
78 LIST_ENTRY(pax_segvguard_uid_entry) sue_list;
79 };
80
81 struct pax_segvguard_entry {
82 LIST_HEAD(, pax_segvguard_uid_entry) segv_uids;
83 };
84
85 static void pax_segvguard_cb(void *);
86 #endif /* PAX_SEGVGUARD */
87
88 /* PaX internal setspecific flags */
89 #define PAX_MPROTECT_EXPLICIT_ENABLE (void *)0x01
90 #define PAX_MPROTECT_EXPLICIT_DISABLE (void *)0x02
91 #define PAX_SEGVGUARD_EXPLICIT_ENABLE (void *)0x03
92 #define PAX_SEGVGUARD_EXPLICIT_DISABLE (void *)0x04
93
94 SYSCTL_SETUP(sysctl_security_pax_setup, "sysctl security.pax setup")
95 {
96 const struct sysctlnode *rnode = NULL, *cnode;
97
98 sysctl_createv(clog, 0, NULL, &rnode,
99 CTLFLAG_PERMANENT,
100 CTLTYPE_NODE, "security", NULL,
101 NULL, 0, NULL, 0,
102 CTL_SECURITY, CTL_EOL);
103
104 sysctl_createv(clog, 0, &rnode, &rnode,
105 CTLFLAG_PERMANENT,
106 CTLTYPE_NODE, "pax",
107 SYSCTL_DESCR("PaX (exploit mitigation) features."),
108 NULL, 0, NULL, 0,
109 CTL_CREATE, CTL_EOL);
110
111 cnode = rnode;
112
113 #ifdef PAX_MPROTECT
114 sysctl_createv(clog, 0, &rnode, &rnode,
115 CTLFLAG_PERMANENT,
116 CTLTYPE_NODE, "mprotect",
117 SYSCTL_DESCR("mprotect(2) W^X restrictions."),
118 NULL, 0, NULL, 0,
119 CTL_CREATE, CTL_EOL);
120 sysctl_createv(clog, 0, &rnode, NULL,
121 CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
122 CTLTYPE_INT, "enabled",
123 SYSCTL_DESCR("Restrictions enabled."),
124 NULL, 0, &pax_mprotect_enabled, 0,
125 CTL_CREATE, CTL_EOL);
126 sysctl_createv(clog, 0, &rnode, NULL,
127 CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
128 CTLTYPE_INT, "global",
129 SYSCTL_DESCR("When enabled, unless explicitly "
130 "specified, apply restrictions to "
131 "all processes."),
132 NULL, 0, &pax_mprotect_global, 0,
133 CTL_CREATE, CTL_EOL);
134 #endif /* PAX_MPROTECT */
135
136 rnode = cnode;
137
138 #ifdef PAX_SEGVGUARD
139 sysctl_createv(clog, 0, &rnode, &rnode,
140 CTLFLAG_PERMANENT,
141 CTLTYPE_NODE, "segvguard",
142 SYSCTL_DESCR("PaX segvguard."),
143 NULL, 0, NULL, 0,
144 CTL_CREATE, CTL_EOL);
145 sysctl_createv(clog, 0, &rnode, NULL,
146 CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
147 CTLTYPE_INT, "enabled",
148 SYSCTL_DESCR("segvguard enabled."),
149 NULL, 0, &pax_segvguard_enabled, 0,
150 CTL_CREATE, CTL_EOL);
151 sysctl_createv(clog, 0, &rnode, NULL,
152 CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
153 CTLTYPE_INT, "global",
154 SYSCTL_DESCR("segvguard all programs."),
155 NULL, 0, &pax_segvguard_global, 0,
156 CTL_CREATE, CTL_EOL);
157 sysctl_createv(clog, 0, &rnode, NULL,
158 CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
159 CTLTYPE_INT, "expiry_timeout",
160 SYSCTL_DESCR("Entry expiry timeout (in seconds)."),
161 NULL, 0, &pax_segvguard_expiry, 0,
162 CTL_CREATE, CTL_EOL);
163 sysctl_createv(clog, 0, &rnode, NULL,
164 CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
165 CTLTYPE_INT, "suspend_timeout",
166 SYSCTL_DESCR("Entry suspension timeout (in seconds)."),
167 NULL, 0, &pax_segvguard_suspension, 0,
168 CTL_CREATE, CTL_EOL);
169 sysctl_createv(clog, 0, &rnode, NULL,
170 CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
171 CTLTYPE_INT, "max_crashes",
172 SYSCTL_DESCR("Max number of crashes before expiry."),
173 NULL, 0, &pax_segvguard_maxcrashes, 0,
174 CTL_CREATE, CTL_EOL);
175 #endif /* PAX_SEGVGUARD */
176 }
177
178 /*
179 * Initialize PaX.
180 */
181 void
182 pax_init(void)
183 {
184 #ifdef PAX_SEGVGUARD
185 int error;
186 #endif /* PAX_SEGVGUARD */
187
188 #ifdef PAX_MPROTECT
189 proc_specific_key_create(&pax_mprotect_key, NULL);
190 #endif /* PAX_MPROTECT */
191
192 #ifdef PAX_SEGVGUARD
193 error = fileassoc_register("segvguard", pax_segvguard_cb,
194 &segvguard_id);
195 if (error) {
196 panic("pax_init: segvguard_id: error=%d\n", error);
197 }
198 proc_specific_key_create(&pax_segvguard_key, NULL);
199 #endif /* PAX_SEGVGUARD */
200 }
201
202 void
203 pax_adjust(struct lwp *l, int f)
204 {
205 #ifdef PAX_MPROTECT
206 if (pax_mprotect_enabled) {
207 if (f & PF_PAXMPROTECT)
208 proc_setspecific(l->l_proc, pax_mprotect_key,
209 PAX_MPROTECT_EXPLICIT_ENABLE);
210 if (f & PF_PAXNOMPROTECT)
211 proc_setspecific(l->l_proc, pax_mprotect_key,
212 PAX_MPROTECT_EXPLICIT_DISABLE);
213 }
214 #endif /* PAX_MPROTECT */
215
216 #ifdef PAX_SEGVGUARD
217 if (pax_segvguard_enabled) {
218 if (f & PF_PAXGUARD) {
219 proc_setspecific(l->l_proc, pax_segvguard_key,
220 PAX_SEGVGUARD_EXPLICIT_ENABLE);
221 }
222 if (f & PF_PAXNOGUARD)
223 proc_setspecific(l->l_proc, pax_segvguard_key,
224 PAX_SEGVGUARD_EXPLICIT_DISABLE);
225 }
226 #endif /* PAX_SEGVGUARD */
227 }
228
229 #ifdef PAX_MPROTECT
230 void
231 pax_mprotect(struct lwp *l, vm_prot_t *prot, vm_prot_t *maxprot)
232 {
233 void *t;
234
235 if (!pax_mprotect_enabled)
236 return;
237
238 t = proc_getspecific(l->l_proc, pax_mprotect_key);
239 if ((pax_mprotect_global && t == PAX_MPROTECT_EXPLICIT_DISABLE) ||
240 (!pax_mprotect_global && t != PAX_MPROTECT_EXPLICIT_ENABLE))
241 return;
242
243 if ((*prot & (VM_PROT_WRITE|VM_PROT_EXECUTE)) != VM_PROT_EXECUTE) {
244 *prot &= ~VM_PROT_EXECUTE;
245 *maxprot &= ~VM_PROT_EXECUTE;
246 } else {
247 *prot &= ~VM_PROT_WRITE;
248 *maxprot &= ~VM_PROT_WRITE;
249 }
250 }
251 #endif /* PAX_MPROTECT */
252
253 #ifdef PAX_SEGVGUARD
254 static void
255 pax_segvguard_cb(void *v)
256 {
257 struct pax_segvguard_entry *p;
258 struct pax_segvguard_uid_entry *up;
259
260 if (v == NULL)
261 return;
262
263 p = v;
264 while ((up = LIST_FIRST(&p->segv_uids)) != NULL) {
265 LIST_REMOVE(up, sue_list);
266 free(up, M_TEMP);
267 }
268
269 free(v, M_TEMP);
270 }
271
272 /*
273 * Called when a process of image vp generated a segfault.
274 */
275 int
276 pax_segvguard(struct lwp *l, struct vnode *vp, const char *name,
277 bool crashed)
278 {
279 struct pax_segvguard_entry *p;
280 struct pax_segvguard_uid_entry *up;
281 struct timeval tv;
282 uid_t uid;
283 void *t;
284 bool have_uid;
285
286 if (!pax_segvguard_enabled)
287 return (0);
288
289 t = proc_getspecific(l->l_proc, pax_segvguard_key);
290 if ((pax_segvguard_global && t == PAX_SEGVGUARD_EXPLICIT_DISABLE) ||
291 (!pax_segvguard_global && t != PAX_SEGVGUARD_EXPLICIT_ENABLE))
292 return (0);
293
294 if (vp == NULL)
295 return (EFAULT);
296
297 /* Check if we already monitor the file. */
298 p = fileassoc_lookup(vp, segvguard_id);
299
300 /* Fast-path if starting a program we don't know. */
301 if (p == NULL && !crashed)
302 return (0);
303
304 microtime(&tv);
305
306 /*
307 * If a program we don't know crashed, we need to create a new entry
308 * for it.
309 */
310 if (p == NULL) {
311 p = malloc(sizeof(*p), M_TEMP, M_WAITOK);
312 fileassoc_add(vp, segvguard_id, p);
313 LIST_INIT(&p->segv_uids);
314
315 /*
316 * Initialize a new entry with "crashes so far" of 1.
317 * The expiry time is when we purge the entry if it didn't
318 * reach the limit.
319 */
320 up = malloc(sizeof(*up), M_TEMP, M_WAITOK);
321 up->sue_uid = kauth_cred_getuid(l->l_cred);
322 up->sue_ncrashes = 1;
323 up->sue_expiry = tv.tv_sec + pax_segvguard_expiry;
324 up->sue_suspended = 0;
325
326 LIST_INSERT_HEAD(&p->segv_uids, up, sue_list);
327
328 return (0);
329 }
330
331 /*
332 * A program we "know" either executed or crashed again.
333 * See if it's a culprit we're familiar with.
334 */
335 uid = kauth_cred_getuid(l->l_cred);
336 have_uid = FALSE;
337 LIST_FOREACH(up, &p->segv_uids, sue_list) {
338 if (up->sue_uid == uid) {
339 have_uid = TRUE;
340 break;
341 }
342 }
343
344 /*
345 * It's someone else. Add an entry for him if we crashed.
346 */
347 if (!have_uid) {
348 if (crashed) {
349 up = malloc(sizeof(*up), M_TEMP, M_WAITOK);
350 up->sue_uid = uid;
351 up->sue_ncrashes = 1;
352 up->sue_expiry = tv.tv_sec + pax_segvguard_expiry;
353 up->sue_suspended = 0;
354
355 LIST_INSERT_HEAD(&p->segv_uids, up, sue_list);
356 }
357
358 return (0);
359 }
360
361 if (crashed) {
362 /* Check if timer on previous crashes expired first. */
363 if (up->sue_expiry < tv.tv_sec) {
364 log(LOG_INFO, "PaX Segvguard: [%s] Suspension"
365 " expired.\n", name ? name : "unknown");
366
367 up->sue_ncrashes = 1;
368 up->sue_expiry = tv.tv_sec + pax_segvguard_expiry;
369 up->sue_suspended = 0;
370
371 return (0);
372 }
373
374 up->sue_ncrashes++;
375
376 if (up->sue_ncrashes >= pax_segvguard_maxcrashes) {
377 log(LOG_ALERT, "PaX Segvguard: [%s] Suspending "
378 "execution for %d seconds after %zu crashes.\n",
379 name ? name : "unknown", pax_segvguard_suspension,
380 up->sue_ncrashes);
381
382 /* Suspend this program for a while. */
383 up->sue_suspended = tv.tv_sec + pax_segvguard_suspension;
384 up->sue_ncrashes = 0;
385 up->sue_expiry = 0;
386 }
387 } else {
388 /* Are we supposed to be suspended? */
389 if (up->sue_suspended > tv.tv_sec) {
390 log(LOG_ALERT, "PaX Segvguard: [%s] Preventing "
391 "execution due to repeated segfaults.\n", name ?
392 name : "unknown");
393
394 return (EPERM);
395 }
396 }
397
398 return (0);
399 }
400 #endif /* PAX_SEGVGUARD */
401