kern_pax.c revision 1.16 1 /* $NetBSD: kern_pax.c,v 1.16 2007/06/24 20:35:37 christos 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, uint32_t f)
204 {
205 #ifdef PAX_MPROTECT
206 if (pax_mprotect_enabled) {
207 if (f & ELF_NOTE_PAX_MPROTECT)
208 proc_setspecific(l->l_proc, pax_mprotect_key,
209 PAX_MPROTECT_EXPLICIT_ENABLE);
210 if (f & ELF_NOTE_PAX_NOMPROTECT)
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 & ELF_NOTE_PAX_GUARD)
219 proc_setspecific(l->l_proc, pax_segvguard_key,
220 PAX_SEGVGUARD_EXPLICIT_ENABLE);
221 if (f & ELF_NOTE_PAX_NOGUARD)
222 proc_setspecific(l->l_proc, pax_segvguard_key,
223 PAX_SEGVGUARD_EXPLICIT_DISABLE);
224 }
225 #endif /* PAX_SEGVGUARD */
226 }
227
228 #ifdef PAX_MPROTECT
229 void
230 pax_mprotect(struct lwp *l, vm_prot_t *prot, vm_prot_t *maxprot)
231 {
232 void *t;
233
234 if (!pax_mprotect_enabled)
235 return;
236
237 t = proc_getspecific(l->l_proc, pax_mprotect_key);
238 if ((pax_mprotect_global && t == PAX_MPROTECT_EXPLICIT_DISABLE) ||
239 (!pax_mprotect_global && t != PAX_MPROTECT_EXPLICIT_ENABLE))
240 return;
241
242 if ((*prot & (VM_PROT_WRITE|VM_PROT_EXECUTE)) != VM_PROT_EXECUTE) {
243 *prot &= ~VM_PROT_EXECUTE;
244 *maxprot &= ~VM_PROT_EXECUTE;
245 } else {
246 *prot &= ~VM_PROT_WRITE;
247 *maxprot &= ~VM_PROT_WRITE;
248 }
249 }
250 #endif /* PAX_MPROTECT */
251
252 #ifdef PAX_SEGVGUARD
253 static void
254 pax_segvguard_cb(void *v)
255 {
256 struct pax_segvguard_entry *p;
257 struct pax_segvguard_uid_entry *up;
258
259 if (v == NULL)
260 return;
261
262 p = v;
263 while ((up = LIST_FIRST(&p->segv_uids)) != NULL) {
264 LIST_REMOVE(up, sue_list);
265 free(up, M_TEMP);
266 }
267
268 free(v, M_TEMP);
269 }
270
271 /*
272 * Called when a process of image vp generated a segfault.
273 */
274 int
275 pax_segvguard(struct lwp *l, struct vnode *vp, const char *name,
276 bool crashed)
277 {
278 struct pax_segvguard_entry *p;
279 struct pax_segvguard_uid_entry *up;
280 struct timeval tv;
281 uid_t uid;
282 void *t;
283 bool have_uid;
284
285 if (!pax_segvguard_enabled)
286 return (0);
287
288 t = proc_getspecific(l->l_proc, pax_segvguard_key);
289 if ((pax_segvguard_global && t == PAX_SEGVGUARD_EXPLICIT_DISABLE) ||
290 (!pax_segvguard_global && t != PAX_SEGVGUARD_EXPLICIT_ENABLE))
291 return (0);
292
293 if (vp == NULL)
294 return (EFAULT);
295
296 /* Check if we already monitor the file. */
297 p = fileassoc_lookup(vp, segvguard_id);
298
299 /* Fast-path if starting a program we don't know. */
300 if (p == NULL && !crashed)
301 return (0);
302
303 microtime(&tv);
304
305 /*
306 * If a program we don't know crashed, we need to create a new entry
307 * for it.
308 */
309 if (p == NULL) {
310 p = malloc(sizeof(*p), M_TEMP, M_WAITOK);
311 fileassoc_add(vp, segvguard_id, p);
312 LIST_INIT(&p->segv_uids);
313
314 /*
315 * Initialize a new entry with "crashes so far" of 1.
316 * The expiry time is when we purge the entry if it didn't
317 * reach the limit.
318 */
319 up = malloc(sizeof(*up), M_TEMP, M_WAITOK);
320 up->sue_uid = kauth_cred_getuid(l->l_cred);
321 up->sue_ncrashes = 1;
322 up->sue_expiry = tv.tv_sec + pax_segvguard_expiry;
323 up->sue_suspended = 0;
324
325 LIST_INSERT_HEAD(&p->segv_uids, up, sue_list);
326
327 return (0);
328 }
329
330 /*
331 * A program we "know" either executed or crashed again.
332 * See if it's a culprit we're familiar with.
333 */
334 uid = kauth_cred_getuid(l->l_cred);
335 have_uid = false;
336 LIST_FOREACH(up, &p->segv_uids, sue_list) {
337 if (up->sue_uid == uid) {
338 have_uid = true;
339 break;
340 }
341 }
342
343 /*
344 * It's someone else. Add an entry for him if we crashed.
345 */
346 if (!have_uid) {
347 if (crashed) {
348 up = malloc(sizeof(*up), M_TEMP, M_WAITOK);
349 up->sue_uid = uid;
350 up->sue_ncrashes = 1;
351 up->sue_expiry = tv.tv_sec + pax_segvguard_expiry;
352 up->sue_suspended = 0;
353
354 LIST_INSERT_HEAD(&p->segv_uids, up, sue_list);
355 }
356
357 return (0);
358 }
359
360 if (crashed) {
361 /* Check if timer on previous crashes expired first. */
362 if (up->sue_expiry < tv.tv_sec) {
363 log(LOG_INFO, "PaX Segvguard: [%s] Suspension"
364 " expired.\n", name ? name : "unknown");
365
366 up->sue_ncrashes = 1;
367 up->sue_expiry = tv.tv_sec + pax_segvguard_expiry;
368 up->sue_suspended = 0;
369
370 return (0);
371 }
372
373 up->sue_ncrashes++;
374
375 if (up->sue_ncrashes >= pax_segvguard_maxcrashes) {
376 log(LOG_ALERT, "PaX Segvguard: [%s] Suspending "
377 "execution for %d seconds after %zu crashes.\n",
378 name ? name : "unknown", pax_segvguard_suspension,
379 up->sue_ncrashes);
380
381 /* Suspend this program for a while. */
382 up->sue_suspended = tv.tv_sec + pax_segvguard_suspension;
383 up->sue_ncrashes = 0;
384 up->sue_expiry = 0;
385 }
386 } else {
387 /* Are we supposed to be suspended? */
388 if (up->sue_suspended > tv.tv_sec) {
389 log(LOG_ALERT, "PaX Segvguard: [%s] Preventing "
390 "execution due to repeated segfaults.\n", name ?
391 name : "unknown");
392
393 return (EPERM);
394 }
395 }
396
397 return (0);
398 }
399 #endif /* PAX_SEGVGUARD */
400