1 | /* $NetBSD: kern_veriexec.c,v 1.11 2015/08/04 12:44:04 maxv Exp $ */ |
2 | |
3 | /*- |
4 | * Copyright (c) 2005, 2006 Elad Efrat <elad@NetBSD.org> |
5 | * Copyright (c) 2005, 2006 Brett Lymn <blymn@NetBSD.org> |
6 | * All rights reserved. |
7 | * |
8 | * Redistribution and use in source and binary forms, with or without |
9 | * modification, are permitted provided that the following conditions |
10 | * are met: |
11 | * 1. Redistributions of source code must retain the above copyright |
12 | * notice, this list of conditions and the following disclaimer. |
13 | * 2. Redistributions in binary form must reproduce the above copyright |
14 | * notice, this list of conditions and the following disclaimer in the |
15 | * documentation and/or other materials provided with the distribution. |
16 | * 3. The name of the authors may not be used to endorse or promote products |
17 | * derived from this software without specific prior written permission. |
18 | * |
19 | * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR |
20 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
21 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
22 | * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
23 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
24 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
28 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
29 | */ |
30 | |
31 | #include <sys/cdefs.h> |
32 | __KERNEL_RCSID(0, "$NetBSD: kern_veriexec.c,v 1.11 2015/08/04 12:44:04 maxv Exp $" ); |
33 | |
34 | #include "opt_veriexec.h" |
35 | |
36 | #include <sys/param.h> |
37 | #include <sys/mount.h> |
38 | #include <sys/kmem.h> |
39 | #include <sys/vnode.h> |
40 | #include <sys/namei.h> |
41 | #include <sys/once.h> |
42 | #include <sys/proc.h> |
43 | #include <sys/rwlock.h> |
44 | #include <sys/syslog.h> |
45 | #include <sys/sysctl.h> |
46 | #include <sys/inttypes.h> |
47 | #include <sys/verified_exec.h> |
48 | #include <sys/sha1.h> |
49 | #include <sys/sha2.h> |
50 | #include <sys/rmd160.h> |
51 | #include <sys/md5.h> |
52 | #include <sys/fileassoc.h> |
53 | #include <sys/kauth.h> |
54 | #include <sys/conf.h> |
55 | #include <miscfs/specfs/specdev.h> |
56 | #include <prop/proplib.h> |
57 | #include <sys/fcntl.h> |
58 | |
59 | /* Readable values for veriexec_file_report(). */ |
60 | #define REPORT_ALWAYS 0x01 /* Always print */ |
61 | #define REPORT_VERBOSE 0x02 /* Print when verbose >= 1 */ |
62 | #define REPORT_DEBUG 0x04 /* Print when verbose >= 2 (debug) */ |
63 | #define REPORT_PANIC 0x08 /* Call panic() */ |
64 | #define REPORT_ALARM 0x10 /* Alarm - also print pid/uid/.. */ |
65 | #define REPORT_LOGMASK (REPORT_ALWAYS|REPORT_VERBOSE|REPORT_DEBUG) |
66 | |
67 | /* state of locking for veriexec_file_verify */ |
68 | #define VERIEXEC_UNLOCKED 0x00 /* Nothing locked, callee does it */ |
69 | #define VERIEXEC_LOCKED 0x01 /* Global op lock held */ |
70 | |
71 | /* state of file locking for veriexec_file_verify */ |
72 | #define VERIEXEC_FILE_UNLOCKED 0x02 /* Nothing locked, callee does it */ |
73 | #define VERIEXEC_FILE_LOCKED 0x04 /* File locked */ |
74 | |
75 | #define VERIEXEC_RW_UPGRADE(lock) while((rw_tryupgrade(lock)) == 0){}; |
76 | |
77 | struct veriexec_fpops { |
78 | const char *type; |
79 | size_t hash_len; |
80 | size_t context_size; |
81 | veriexec_fpop_init_t init; |
82 | veriexec_fpop_update_t update; |
83 | veriexec_fpop_final_t final; |
84 | LIST_ENTRY(veriexec_fpops) entries; |
85 | }; |
86 | |
87 | /* Veriexec per-file entry data. */ |
88 | struct veriexec_file_entry { |
89 | krwlock_t lock; /* r/w lock */ |
90 | u_char *filename; /* File name. */ |
91 | u_char type; /* Entry type. */ |
92 | u_char status; /* Evaluation status. */ |
93 | u_char *fp; /* Fingerprint. */ |
94 | struct veriexec_fpops *ops; /* Fingerprint ops vector*/ |
95 | size_t filename_len; /* Length of filename. */ |
96 | }; |
97 | |
98 | /* Veriexec per-table data. */ |
99 | struct veriexec_table_entry { |
100 | uint64_t vte_count; /* Number of Veriexec entries. */ |
101 | const struct sysctlnode *vte_node; |
102 | }; |
103 | |
104 | static int veriexec_verbose; |
105 | static int veriexec_strict; |
106 | static int veriexec_bypass = 1; |
107 | |
108 | static char *veriexec_fp_names = NULL; |
109 | static size_t veriexec_name_max = 0; |
110 | |
111 | static const struct sysctlnode *veriexec_count_node; |
112 | |
113 | static fileassoc_t veriexec_hook; |
114 | static specificdata_key_t veriexec_mountspecific_key; |
115 | |
116 | static LIST_HEAD(, veriexec_fpops) veriexec_fpops_list = |
117 | LIST_HEAD_INITIALIZER(veriexec_fpops_list); |
118 | |
119 | static int veriexec_raw_cb(kauth_cred_t, kauth_action_t, void *, |
120 | void *, void *, void *, void *); |
121 | static struct veriexec_fpops *veriexec_fpops_lookup(const char *); |
122 | static void veriexec_file_free(struct veriexec_file_entry *); |
123 | |
124 | static unsigned int veriexec_tablecount = 0; |
125 | |
126 | /* |
127 | * Veriexec operations global lock - most ops hold this as a read |
128 | * lock, it is upgraded to a write lock when destroying veriexec file |
129 | * table entries. |
130 | */ |
131 | static krwlock_t veriexec_op_lock; |
132 | |
133 | /* |
134 | * Sysctl helper routine for Veriexec. |
135 | */ |
136 | static int |
137 | sysctl_kern_veriexec_algorithms(SYSCTLFN_ARGS) |
138 | { |
139 | size_t len; |
140 | int error; |
141 | const char *p; |
142 | |
143 | if (newp != NULL) |
144 | return EPERM; |
145 | |
146 | if (namelen != 0) |
147 | return EINVAL; |
148 | |
149 | p = veriexec_fp_names == NULL ? "" : veriexec_fp_names; |
150 | |
151 | len = strlen(p) + 1; |
152 | |
153 | if (*oldlenp < len && oldp) |
154 | return ENOMEM; |
155 | |
156 | if (oldp && (error = copyout(p, oldp, len)) != 0) |
157 | return error; |
158 | |
159 | *oldlenp = len; |
160 | return 0; |
161 | } |
162 | |
163 | static int |
164 | sysctl_kern_veriexec_strict(SYSCTLFN_ARGS) |
165 | { |
166 | struct sysctlnode node; |
167 | int error, newval; |
168 | |
169 | node = *rnode; |
170 | node.sysctl_data = &newval; |
171 | |
172 | newval = veriexec_strict; |
173 | error = sysctl_lookup(SYSCTLFN_CALL(&node)); |
174 | if (error || newp == NULL) |
175 | return error; |
176 | |
177 | if (newval < veriexec_strict) |
178 | return EPERM; |
179 | |
180 | veriexec_strict = newval; |
181 | |
182 | return 0; |
183 | } |
184 | |
185 | SYSCTL_SETUP(sysctl_kern_veriexec_setup, "sysctl kern.veriexec setup" ) |
186 | { |
187 | const struct sysctlnode *rnode = NULL; |
188 | |
189 | sysctl_createv(clog, 0, NULL, &rnode, |
190 | CTLFLAG_PERMANENT, |
191 | CTLTYPE_NODE, "veriexec" , |
192 | SYSCTL_DESCR("Veriexec" ), |
193 | NULL, 0, NULL, 0, |
194 | CTL_KERN, CTL_CREATE, CTL_EOL); |
195 | |
196 | sysctl_createv(clog, 0, &rnode, NULL, |
197 | CTLFLAG_PERMANENT|CTLFLAG_READWRITE, |
198 | CTLTYPE_INT, "verbose" , |
199 | SYSCTL_DESCR("Veriexec verbose level" ), |
200 | NULL, 0, &veriexec_verbose, 0, |
201 | CTL_CREATE, CTL_EOL); |
202 | sysctl_createv(clog, 0, &rnode, NULL, |
203 | CTLFLAG_PERMANENT|CTLFLAG_READWRITE, |
204 | CTLTYPE_INT, "strict" , |
205 | SYSCTL_DESCR("Veriexec strict level" ), |
206 | sysctl_kern_veriexec_strict, 0, NULL, 0, |
207 | CTL_CREATE, CTL_EOL); |
208 | sysctl_createv(clog, 0, &rnode, NULL, |
209 | CTLFLAG_PERMANENT, |
210 | CTLTYPE_STRING, "algorithms" , |
211 | SYSCTL_DESCR("Veriexec supported hashing " |
212 | "algorithms" ), |
213 | sysctl_kern_veriexec_algorithms, 0, NULL, 0, |
214 | CTL_CREATE, CTL_EOL); |
215 | sysctl_createv(clog, 0, &rnode, &veriexec_count_node, |
216 | CTLFLAG_PERMANENT, |
217 | CTLTYPE_NODE, "count" , |
218 | SYSCTL_DESCR("Number of fingerprints on mount(s)" ), |
219 | NULL, 0, NULL, 0, |
220 | CTL_CREATE, CTL_EOL); |
221 | } |
222 | |
223 | /* |
224 | * Add ops to the fingerprint ops vector list. |
225 | */ |
226 | int |
227 | veriexec_fpops_add(const char *fp_type, size_t hash_len, size_t ctx_size, |
228 | veriexec_fpop_init_t init, veriexec_fpop_update_t update, |
229 | veriexec_fpop_final_t final) |
230 | { |
231 | struct veriexec_fpops *ops; |
232 | |
233 | KASSERT((init != NULL) && (update != NULL) && (final != NULL)); |
234 | KASSERT((hash_len != 0) && (ctx_size != 0)); |
235 | KASSERT(fp_type != NULL); |
236 | |
237 | if (veriexec_fpops_lookup(fp_type) != NULL) |
238 | return (EEXIST); |
239 | |
240 | ops = kmem_alloc(sizeof(*ops), KM_SLEEP); |
241 | ops->type = fp_type; |
242 | ops->hash_len = hash_len; |
243 | ops->context_size = ctx_size; |
244 | ops->init = init; |
245 | ops->update = update; |
246 | ops->final = final; |
247 | |
248 | LIST_INSERT_HEAD(&veriexec_fpops_list, ops, entries); |
249 | |
250 | /* |
251 | * If we don't have space for any names, allocate enough for six |
252 | * which should be sufficient. (it's also enough for all algorithms |
253 | * we can support at the moment) |
254 | */ |
255 | if (veriexec_fp_names == NULL) { |
256 | veriexec_name_max = 64; |
257 | veriexec_fp_names = kmem_zalloc(veriexec_name_max, KM_SLEEP); |
258 | } |
259 | |
260 | /* |
261 | * If we're running out of space for storing supported algorithms, |
262 | * extend the buffer with space for four names. |
263 | */ |
264 | while (veriexec_name_max - (strlen(veriexec_fp_names) + 1) < |
265 | strlen(fp_type)) { |
266 | char *newp; |
267 | unsigned int new_max; |
268 | |
269 | /* Add space for four algorithm names. */ |
270 | new_max = veriexec_name_max + 64; |
271 | newp = kmem_zalloc(new_max, KM_SLEEP); |
272 | strlcpy(newp, veriexec_fp_names, new_max); |
273 | kmem_free(veriexec_fp_names, veriexec_name_max); |
274 | veriexec_fp_names = newp; |
275 | veriexec_name_max = new_max; |
276 | } |
277 | |
278 | if (*veriexec_fp_names != '\0') |
279 | strlcat(veriexec_fp_names, " " , veriexec_name_max); |
280 | |
281 | strlcat(veriexec_fp_names, fp_type, veriexec_name_max); |
282 | |
283 | return (0); |
284 | } |
285 | |
286 | static void |
287 | veriexec_mountspecific_dtor(void *v) |
288 | { |
289 | struct veriexec_table_entry *vte = v; |
290 | |
291 | if (vte == NULL) { |
292 | return; |
293 | } |
294 | sysctl_free(__UNCONST(vte->vte_node)); |
295 | veriexec_tablecount--; |
296 | kmem_free(vte, sizeof(*vte)); |
297 | } |
298 | |
299 | static int |
300 | veriexec_listener_cb(kauth_cred_t cred, kauth_action_t action, void *cookie, |
301 | void *arg0, void *arg1, void *arg2, void *arg3) |
302 | { |
303 | int result; |
304 | enum kauth_system_req req; |
305 | |
306 | if (action != KAUTH_SYSTEM_VERIEXEC) |
307 | return KAUTH_RESULT_DEFER; |
308 | |
309 | result = KAUTH_RESULT_DEFER; |
310 | req = (enum kauth_system_req)arg0; |
311 | |
312 | if (req == KAUTH_REQ_SYSTEM_VERIEXEC_MODIFY && |
313 | veriexec_strict > VERIEXEC_LEARNING) { |
314 | log(LOG_WARNING, "Veriexec: Strict mode, modifying " |
315 | "tables not permitted.\n" ); |
316 | |
317 | result = KAUTH_RESULT_DENY; |
318 | } |
319 | |
320 | return result; |
321 | } |
322 | |
323 | /* |
324 | * Initialise Veriexec. |
325 | */ |
326 | void |
327 | veriexec_init(void) |
328 | { |
329 | int error; |
330 | |
331 | /* Register a fileassoc for Veriexec. */ |
332 | error = fileassoc_register("veriexec" , |
333 | (fileassoc_cleanup_cb_t)veriexec_file_free, &veriexec_hook); |
334 | if (error) |
335 | panic("Veriexec: Can't register fileassoc: error=%d" , error); |
336 | |
337 | /* Register listener to handle raw disk access. */ |
338 | if (kauth_listen_scope(KAUTH_SCOPE_DEVICE, veriexec_raw_cb, NULL) == |
339 | NULL) |
340 | panic("Veriexec: Can't listen on device scope" ); |
341 | |
342 | error = mount_specific_key_create(&veriexec_mountspecific_key, |
343 | veriexec_mountspecific_dtor); |
344 | if (error) |
345 | panic("Veriexec: Can't create mountspecific key" ); |
346 | |
347 | if (kauth_listen_scope(KAUTH_SCOPE_SYSTEM, veriexec_listener_cb, |
348 | NULL) == NULL) |
349 | panic("Veriexec: Can't listen on system scope" ); |
350 | |
351 | rw_init(&veriexec_op_lock); |
352 | |
353 | #define FPOPS_ADD(a, b, c, d, e, f) \ |
354 | veriexec_fpops_add(a, b, c, (veriexec_fpop_init_t)d, \ |
355 | (veriexec_fpop_update_t)e, (veriexec_fpop_final_t)f) |
356 | |
357 | #ifdef VERIFIED_EXEC_FP_RMD160 |
358 | FPOPS_ADD("RMD160" , RMD160_DIGEST_LENGTH, sizeof(RMD160_CTX), |
359 | RMD160Init, RMD160Update, RMD160Final); |
360 | #endif /* VERIFIED_EXEC_FP_RMD160 */ |
361 | |
362 | #ifdef VERIFIED_EXEC_FP_SHA256 |
363 | FPOPS_ADD("SHA256" , SHA256_DIGEST_LENGTH, sizeof(SHA256_CTX), |
364 | SHA256_Init, SHA256_Update, SHA256_Final); |
365 | #endif /* VERIFIED_EXEC_FP_SHA256 */ |
366 | |
367 | #ifdef VERIFIED_EXEC_FP_SHA384 |
368 | FPOPS_ADD("SHA384" , SHA384_DIGEST_LENGTH, sizeof(SHA384_CTX), |
369 | SHA384_Init, SHA384_Update, SHA384_Final); |
370 | #endif /* VERIFIED_EXEC_FP_SHA384 */ |
371 | |
372 | #ifdef VERIFIED_EXEC_FP_SHA512 |
373 | FPOPS_ADD("SHA512" , SHA512_DIGEST_LENGTH, sizeof(SHA512_CTX), |
374 | SHA512_Init, SHA512_Update, SHA512_Final); |
375 | #endif /* VERIFIED_EXEC_FP_SHA512 */ |
376 | |
377 | #ifdef VERIFIED_EXEC_FP_SHA1 |
378 | FPOPS_ADD("SHA1" , SHA1_DIGEST_LENGTH, sizeof(SHA1_CTX), |
379 | SHA1Init, SHA1Update, SHA1Final); |
380 | #endif /* VERIFIED_EXEC_FP_SHA1 */ |
381 | |
382 | #ifdef VERIFIED_EXEC_FP_MD5 |
383 | FPOPS_ADD("MD5" , MD5_DIGEST_LENGTH, sizeof(MD5_CTX), |
384 | MD5Init, MD5Update, MD5Final); |
385 | #endif /* VERIFIED_EXEC_FP_MD5 */ |
386 | |
387 | #undef FPOPS_ADD |
388 | } |
389 | |
390 | static struct veriexec_fpops * |
391 | veriexec_fpops_lookup(const char *name) |
392 | { |
393 | struct veriexec_fpops *ops; |
394 | |
395 | if (name == NULL) |
396 | return (NULL); |
397 | |
398 | LIST_FOREACH(ops, &veriexec_fpops_list, entries) { |
399 | if (strcasecmp(name, ops->type) == 0) |
400 | return (ops); |
401 | } |
402 | |
403 | return (NULL); |
404 | } |
405 | |
406 | /* |
407 | * Calculate fingerprint. Information on hash length and routines used is |
408 | * extracted from veriexec_hash_list according to the hash type. |
409 | * |
410 | * NOTE: vfe is assumed to be locked for writing on entry. |
411 | */ |
412 | static int |
413 | veriexec_fp_calc(struct lwp *l, struct vnode *vp, int file_lock_state, |
414 | struct veriexec_file_entry *vfe, u_char *fp) |
415 | { |
416 | struct vattr va; |
417 | void *ctx; |
418 | u_char *buf; |
419 | off_t offset, len; |
420 | size_t resid; |
421 | int error; |
422 | |
423 | KASSERT(file_lock_state != VERIEXEC_LOCKED); |
424 | KASSERT(file_lock_state != VERIEXEC_UNLOCKED); |
425 | |
426 | if (file_lock_state == VERIEXEC_FILE_UNLOCKED) |
427 | vn_lock(vp, LK_SHARED | LK_RETRY); |
428 | error = VOP_GETATTR(vp, &va, l->l_cred); |
429 | if (file_lock_state == VERIEXEC_FILE_UNLOCKED) |
430 | VOP_UNLOCK(vp); |
431 | if (error) |
432 | return (error); |
433 | |
434 | ctx = kmem_alloc(vfe->ops->context_size, KM_SLEEP); |
435 | buf = kmem_alloc(PAGE_SIZE, KM_SLEEP); |
436 | |
437 | (vfe->ops->init)(ctx); |
438 | |
439 | len = 0; |
440 | error = 0; |
441 | for (offset = 0; offset < va.va_size; offset += PAGE_SIZE) { |
442 | len = ((va.va_size - offset) < PAGE_SIZE) ? |
443 | (va.va_size - offset) : PAGE_SIZE; |
444 | |
445 | error = vn_rdwr(UIO_READ, vp, buf, len, offset, |
446 | UIO_SYSSPACE, |
447 | ((file_lock_state == VERIEXEC_FILE_LOCKED)? |
448 | IO_NODELOCKED : 0), |
449 | l->l_cred, &resid, NULL); |
450 | |
451 | if (error) { |
452 | goto bad; |
453 | } |
454 | |
455 | (vfe->ops->update)(ctx, buf, (unsigned int) len); |
456 | |
457 | if (len != PAGE_SIZE) |
458 | break; |
459 | } |
460 | |
461 | (vfe->ops->final)(fp, ctx); |
462 | |
463 | bad: |
464 | kmem_free(ctx, vfe->ops->context_size); |
465 | kmem_free(buf, PAGE_SIZE); |
466 | |
467 | return (error); |
468 | } |
469 | |
470 | /* Compare two fingerprints of the same type. */ |
471 | static int |
472 | veriexec_fp_cmp(struct veriexec_fpops *ops, u_char *fp1, u_char *fp2) |
473 | { |
474 | if (veriexec_verbose >= 2) { |
475 | int i; |
476 | |
477 | printf("comparing hashes...\n" ); |
478 | printf("fp1: " ); |
479 | for (i = 0; i < ops->hash_len; i++) { |
480 | printf("%02x" , fp1[i]); |
481 | } |
482 | printf("\nfp2: " ); |
483 | for (i = 0; i < ops->hash_len; i++) { |
484 | printf("%02x" , fp2[i]); |
485 | } |
486 | printf("\n" ); |
487 | } |
488 | |
489 | return (memcmp(fp1, fp2, ops->hash_len)); |
490 | } |
491 | |
492 | static int |
493 | veriexec_fp_status(struct lwp *l, struct vnode *vp, int file_lock_state, |
494 | struct veriexec_file_entry *vfe, u_char *status) |
495 | { |
496 | size_t hash_len = vfe->ops->hash_len; |
497 | u_char *digest; |
498 | int error; |
499 | |
500 | digest = kmem_zalloc(hash_len, KM_SLEEP); |
501 | |
502 | error = veriexec_fp_calc(l, vp, file_lock_state, vfe, digest); |
503 | if (error) |
504 | goto out; |
505 | |
506 | /* Compare fingerprint with loaded data. */ |
507 | if (veriexec_fp_cmp(vfe->ops, vfe->fp, digest) == 0) |
508 | *status = FINGERPRINT_VALID; |
509 | else |
510 | *status = FINGERPRINT_NOMATCH; |
511 | |
512 | out: |
513 | kmem_free(digest, hash_len); |
514 | return error; |
515 | } |
516 | |
517 | |
518 | static struct veriexec_table_entry * |
519 | veriexec_table_lookup(struct mount *mp) |
520 | { |
521 | /* XXX: From raidframe init */ |
522 | if (mp == NULL) |
523 | return NULL; |
524 | |
525 | return mount_getspecific(mp, veriexec_mountspecific_key); |
526 | } |
527 | |
528 | static struct veriexec_file_entry * |
529 | veriexec_get(struct vnode *vp) |
530 | { |
531 | return (fileassoc_lookup(vp, veriexec_hook)); |
532 | } |
533 | |
534 | bool |
535 | veriexec_lookup(struct vnode *vp) |
536 | { |
537 | return (veriexec_get(vp) == NULL ? false : true); |
538 | } |
539 | |
540 | /* |
541 | * Routine for maintaining mostly consistent message formats in Veriexec. |
542 | */ |
543 | static void |
544 | veriexec_file_report(struct veriexec_file_entry *vfe, const u_char *msg, |
545 | const u_char *filename, struct lwp *l, int f) |
546 | { |
547 | if (vfe != NULL && vfe->filename != NULL) |
548 | filename = vfe->filename; |
549 | if (filename == NULL) |
550 | return; |
551 | |
552 | if (((f & REPORT_LOGMASK) >> 1) <= veriexec_verbose) { |
553 | if (!(f & REPORT_ALARM) || (l == NULL)) |
554 | log(LOG_NOTICE, "Veriexec: %s [%s]\n" , msg, |
555 | filename); |
556 | else |
557 | log(LOG_ALERT, "Veriexec: %s [%s, prog=%s pid=%u, " |
558 | "uid=%u, gid=%u]\n" , msg, filename, |
559 | l->l_proc->p_comm, l->l_proc->p_pid, |
560 | kauth_cred_getuid(l->l_cred), |
561 | kauth_cred_getgid(l->l_cred)); |
562 | } |
563 | |
564 | if (f & REPORT_PANIC) |
565 | panic("Veriexec: Unrecoverable error." ); |
566 | } |
567 | |
568 | /* |
569 | * Verify the fingerprint of the given file. If we're called directly from |
570 | * sys_execve(), 'flag' will be VERIEXEC_DIRECT. If we're called from |
571 | * exec_script(), 'flag' will be VERIEXEC_INDIRECT. If we are called from |
572 | * vn_open(), 'flag' will be VERIEXEC_FILE. |
573 | * |
574 | * 'veriexec_op_lock' must be locked (and remains locked). |
575 | * |
576 | * NOTE: The veriexec file entry pointer (vfep) will be returned LOCKED |
577 | * on no error. |
578 | */ |
579 | static int |
580 | veriexec_file_verify(struct lwp *l, struct vnode *vp, const u_char *name, |
581 | int flag, int file_lock_state, struct veriexec_file_entry **vfep) |
582 | { |
583 | struct veriexec_file_entry *vfe; |
584 | int error = 0; |
585 | |
586 | KASSERT(rw_lock_held(&veriexec_op_lock)); |
587 | KASSERT(file_lock_state != VERIEXEC_LOCKED); |
588 | KASSERT(file_lock_state != VERIEXEC_UNLOCKED); |
589 | |
590 | #define VFE_NEEDS_EVAL(vfe) ((vfe->status == FINGERPRINT_NOTEVAL) || \ |
591 | (vfe->type & VERIEXEC_UNTRUSTED)) |
592 | |
593 | if (vfep != NULL) |
594 | *vfep = NULL; |
595 | |
596 | if (vp->v_type != VREG) |
597 | return (0); |
598 | |
599 | /* Lookup veriexec table entry, save pointer if requested. */ |
600 | vfe = veriexec_get(vp); |
601 | if (vfep != NULL) |
602 | *vfep = vfe; |
603 | |
604 | /* No entry in the veriexec tables. */ |
605 | if (vfe == NULL) { |
606 | veriexec_file_report(NULL, "No entry." , name, |
607 | l, REPORT_VERBOSE); |
608 | |
609 | /* |
610 | * Lockdown mode: Deny access to non-monitored files. |
611 | * IPS mode: Deny execution of non-monitored files. |
612 | */ |
613 | if ((veriexec_strict >= VERIEXEC_LOCKDOWN) || |
614 | ((veriexec_strict >= VERIEXEC_IPS) && |
615 | (flag != VERIEXEC_FILE))) |
616 | return (EPERM); |
617 | |
618 | return (0); |
619 | } |
620 | |
621 | /* |
622 | * Grab the lock for the entry, if we need to do an evaluation |
623 | * then the lock is a write lock, after we have the write |
624 | * lock, check if we really need it - some other thread may |
625 | * have already done the work for us. |
626 | */ |
627 | if (VFE_NEEDS_EVAL(vfe)) { |
628 | rw_enter(&vfe->lock, RW_WRITER); |
629 | if (!VFE_NEEDS_EVAL(vfe)) |
630 | rw_downgrade(&vfe->lock); |
631 | } else |
632 | rw_enter(&vfe->lock, RW_READER); |
633 | |
634 | /* Evaluate fingerprint if needed. */ |
635 | if (VFE_NEEDS_EVAL(vfe)) { |
636 | u_char status; |
637 | |
638 | error = veriexec_fp_status(l, vp, file_lock_state, vfe, &status); |
639 | if (error) { |
640 | veriexec_file_report(vfe, "Fingerprint calculation error." , |
641 | name, NULL, REPORT_ALWAYS); |
642 | rw_exit(&vfe->lock); |
643 | return (error); |
644 | } |
645 | vfe->status = status; |
646 | rw_downgrade(&vfe->lock); |
647 | } |
648 | |
649 | if (!(vfe->type & flag)) { |
650 | veriexec_file_report(vfe, "Incorrect access type." , name, l, |
651 | REPORT_ALWAYS|REPORT_ALARM); |
652 | |
653 | /* IPS mode: Enforce access type. */ |
654 | if (veriexec_strict >= VERIEXEC_IPS) { |
655 | rw_exit(&vfe->lock); |
656 | return (EPERM); |
657 | } |
658 | } |
659 | |
660 | switch (vfe->status) { |
661 | case FINGERPRINT_NOTEVAL: |
662 | /* Should not happen. */ |
663 | rw_exit(&vfe->lock); |
664 | veriexec_file_report(vfe, "Not-evaluated status " |
665 | "post evaluation; inconsistency detected." , name, |
666 | NULL, REPORT_ALWAYS|REPORT_PANIC); |
667 | /* NOTREACHED */ |
668 | |
669 | case FINGERPRINT_VALID: |
670 | /* Valid fingerprint. */ |
671 | veriexec_file_report(vfe, "Match." , name, NULL, |
672 | REPORT_VERBOSE); |
673 | |
674 | break; |
675 | |
676 | case FINGERPRINT_NOMATCH: |
677 | /* Fingerprint mismatch. */ |
678 | veriexec_file_report(vfe, "Mismatch." , name, |
679 | NULL, REPORT_ALWAYS|REPORT_ALARM); |
680 | |
681 | /* IDS mode: Deny access on fingerprint mismatch. */ |
682 | if (veriexec_strict >= VERIEXEC_IDS) { |
683 | rw_exit(&vfe->lock); |
684 | error = EPERM; |
685 | } |
686 | |
687 | break; |
688 | |
689 | default: |
690 | /* Should never happen. */ |
691 | rw_exit(&vfe->lock); |
692 | veriexec_file_report(vfe, "Invalid status " |
693 | "post evaluation." , name, NULL, REPORT_ALWAYS|REPORT_PANIC); |
694 | /* NOTREACHED */ |
695 | } |
696 | |
697 | return (error); |
698 | } |
699 | |
700 | int |
701 | veriexec_verify(struct lwp *l, struct vnode *vp, const u_char *name, int flag, |
702 | bool *found) |
703 | { |
704 | struct veriexec_file_entry *vfe; |
705 | int r; |
706 | |
707 | if (veriexec_bypass && (veriexec_strict == VERIEXEC_LEARNING)) |
708 | return 0; |
709 | |
710 | rw_enter(&veriexec_op_lock, RW_READER); |
711 | r = veriexec_file_verify(l, vp, name, flag, VERIEXEC_FILE_UNLOCKED, |
712 | &vfe); |
713 | rw_exit(&veriexec_op_lock); |
714 | |
715 | if ((r == 0) && (vfe != NULL)) |
716 | rw_exit(&vfe->lock); |
717 | |
718 | if (found != NULL) |
719 | *found = (vfe != NULL) ? true : false; |
720 | |
721 | return (r); |
722 | } |
723 | |
724 | /* |
725 | * Veriexec remove policy code. |
726 | */ |
727 | int |
728 | veriexec_removechk(struct lwp *l, struct vnode *vp, const char *pathbuf) |
729 | { |
730 | struct veriexec_file_entry *vfe; |
731 | int error; |
732 | |
733 | if (veriexec_bypass && (veriexec_strict == VERIEXEC_LEARNING)) |
734 | return 0; |
735 | |
736 | rw_enter(&veriexec_op_lock, RW_READER); |
737 | vfe = veriexec_get(vp); |
738 | rw_exit(&veriexec_op_lock); |
739 | |
740 | if (vfe == NULL) { |
741 | /* Lockdown mode: Deny access to non-monitored files. */ |
742 | if (veriexec_strict >= VERIEXEC_LOCKDOWN) |
743 | return (EPERM); |
744 | |
745 | return (0); |
746 | } |
747 | |
748 | veriexec_file_report(vfe, "Remove request." , pathbuf, l, |
749 | REPORT_ALWAYS|REPORT_ALARM); |
750 | |
751 | /* IDS mode: Deny removal of monitored files. */ |
752 | if (veriexec_strict >= VERIEXEC_IDS) |
753 | error = EPERM; |
754 | else |
755 | error = veriexec_file_delete(l, vp); |
756 | |
757 | return error; |
758 | } |
759 | |
760 | /* |
761 | * Veriexec rename policy. |
762 | * |
763 | * XXX: Once there's a way to hook after a successful rename, it would be |
764 | * XXX: nice to update vfe->filename to the new name if it's not NULL and |
765 | * XXX: the new name is absolute (ie., starts with a slash). |
766 | */ |
767 | int |
768 | veriexec_renamechk(struct lwp *l, struct vnode *fromvp, const char *fromname, |
769 | struct vnode *tovp, const char *toname) |
770 | { |
771 | struct veriexec_file_entry *fvfe = NULL, *tvfe = NULL; |
772 | |
773 | if (veriexec_bypass && (veriexec_strict == VERIEXEC_LEARNING)) |
774 | return 0; |
775 | |
776 | rw_enter(&veriexec_op_lock, RW_READER); |
777 | |
778 | if (veriexec_strict >= VERIEXEC_LOCKDOWN) { |
779 | log(LOG_ALERT, "Veriexec: Preventing rename of `%s' to " |
780 | "`%s', uid=%u, pid=%u: Lockdown mode.\n" , fromname, toname, |
781 | kauth_cred_geteuid(l->l_cred), l->l_proc->p_pid); |
782 | rw_exit(&veriexec_op_lock); |
783 | return (EPERM); |
784 | } |
785 | |
786 | fvfe = veriexec_get(fromvp); |
787 | if (tovp != NULL) |
788 | tvfe = veriexec_get(tovp); |
789 | |
790 | if ((fvfe == NULL) && (tvfe == NULL)) { |
791 | /* None of them is monitored */ |
792 | rw_exit(&veriexec_op_lock); |
793 | return 0; |
794 | } |
795 | |
796 | if (veriexec_strict >= VERIEXEC_IPS) { |
797 | log(LOG_ALERT, "Veriexec: Preventing rename of `%s' " |
798 | "to `%s', uid=%u, pid=%u: IPS mode, %s " |
799 | "monitored.\n" , fromname, toname, |
800 | kauth_cred_geteuid(l->l_cred), |
801 | l->l_proc->p_pid, (fvfe != NULL && tvfe != NULL) ? |
802 | "files" : "file" ); |
803 | rw_exit(&veriexec_op_lock); |
804 | return (EPERM); |
805 | } |
806 | |
807 | if (fvfe != NULL) { |
808 | /* |
809 | * Monitored file is renamed; filename no longer relevant. |
810 | */ |
811 | |
812 | /* |
813 | * XXX: We could keep the buffer, and when (and if) updating the |
814 | * XXX: filename post-rename, re-allocate it only if it's not |
815 | * XXX: big enough for the new filename. |
816 | */ |
817 | |
818 | /* XXX: Get write lock on fvfe here? */ |
819 | |
820 | VERIEXEC_RW_UPGRADE(&veriexec_op_lock); |
821 | /* once we have the op lock in write mode |
822 | * there should be no locks on any file |
823 | * entries so we can destroy the object. |
824 | */ |
825 | |
826 | if (fvfe->filename_len > 0) |
827 | kmem_free(fvfe->filename, fvfe->filename_len); |
828 | |
829 | fvfe->filename = NULL; |
830 | fvfe->filename_len = 0; |
831 | |
832 | rw_downgrade(&veriexec_op_lock); |
833 | } |
834 | |
835 | log(LOG_NOTICE, "Veriexec: %s file `%s' renamed to " |
836 | "%s file `%s', uid=%u, pid=%u.\n" , (fvfe != NULL) ? |
837 | "Monitored" : "Non-monitored" , fromname, (tvfe != NULL) ? |
838 | "monitored" : "non-monitored" , toname, |
839 | kauth_cred_geteuid(l->l_cred), l->l_proc->p_pid); |
840 | |
841 | rw_exit(&veriexec_op_lock); |
842 | |
843 | if (tvfe != NULL) { |
844 | /* |
845 | * Monitored file is overwritten. Remove the entry. |
846 | */ |
847 | (void)veriexec_file_delete(l, tovp); |
848 | } |
849 | |
850 | return (0); |
851 | } |
852 | |
853 | static void |
854 | veriexec_file_free(struct veriexec_file_entry *vfe) |
855 | { |
856 | if (vfe != NULL) { |
857 | if (vfe->fp != NULL) |
858 | kmem_free(vfe->fp, vfe->ops->hash_len); |
859 | if (vfe->filename != NULL) |
860 | kmem_free(vfe->filename, vfe->filename_len); |
861 | rw_destroy(&vfe->lock); |
862 | kmem_free(vfe, sizeof(*vfe)); |
863 | } |
864 | } |
865 | |
866 | static void |
867 | veriexec_file_purge(struct veriexec_file_entry *vfe, int have_lock) |
868 | { |
869 | if (vfe == NULL) |
870 | return; |
871 | |
872 | if (have_lock == VERIEXEC_UNLOCKED) |
873 | rw_enter(&vfe->lock, RW_WRITER); |
874 | else |
875 | VERIEXEC_RW_UPGRADE(&vfe->lock); |
876 | |
877 | vfe->status = FINGERPRINT_NOTEVAL; |
878 | if (have_lock == VERIEXEC_UNLOCKED) |
879 | rw_exit(&vfe->lock); |
880 | else |
881 | rw_downgrade(&vfe->lock); |
882 | } |
883 | |
884 | static void |
885 | veriexec_file_purge_cb(struct veriexec_file_entry *vfe, void *cookie) |
886 | { |
887 | veriexec_file_purge(vfe, VERIEXEC_UNLOCKED); |
888 | } |
889 | |
890 | /* |
891 | * Invalidate a Veriexec file entry. |
892 | * XXX: This should be updated when per-page fingerprints are added. |
893 | */ |
894 | void |
895 | veriexec_purge(struct vnode *vp) |
896 | { |
897 | rw_enter(&veriexec_op_lock, RW_READER); |
898 | veriexec_file_purge(veriexec_get(vp), VERIEXEC_UNLOCKED); |
899 | rw_exit(&veriexec_op_lock); |
900 | } |
901 | |
902 | /* |
903 | * Enforce raw disk access policy. |
904 | * |
905 | * IDS mode: Invalidate fingerprints on a mount if it's opened for writing. |
906 | * IPS mode: Don't allow raw writing to disks we monitor. |
907 | * Lockdown mode: Don't allow raw writing to all disks. |
908 | * |
909 | * XXX: This is bogus. There's an obvious race condition between the time |
910 | * XXX: the disk is open for writing, in which an attacker can access a |
911 | * XXX: monitored file to get its signature cached again, and when the raw |
912 | * XXX: file is overwritten on disk. |
913 | * XXX: |
914 | * XXX: To solve this, we need something like the following: |
915 | * XXX: open raw disk: |
916 | * XXX: - raise refcount, |
917 | * XXX: - invalidate fingerprints, |
918 | * XXX: - mark all entries for that disk with "no cache" flag |
919 | * XXX: |
920 | * XXX: veriexec_verify: |
921 | * XXX: - if "no cache", don't cache evaluation result |
922 | * XXX: |
923 | * XXX: close raw disk: |
924 | * XXX: - lower refcount, |
925 | * XXX: - if refcount == 0, remove "no cache" flag from all entries |
926 | */ |
927 | static int |
928 | veriexec_raw_cb(kauth_cred_t cred, kauth_action_t action, void *cookie, |
929 | void *arg0, void *arg1, void *arg2, void *arg3) |
930 | { |
931 | int result; |
932 | enum kauth_device_req req; |
933 | struct veriexec_table_entry *vte; |
934 | |
935 | result = KAUTH_RESULT_DENY; |
936 | req = (enum kauth_device_req)arg0; |
937 | |
938 | switch (action) { |
939 | case KAUTH_DEVICE_RAWIO_SPEC: { |
940 | struct vnode *vp, *bvp; |
941 | int error; |
942 | |
943 | if (req == KAUTH_REQ_DEVICE_RAWIO_SPEC_READ) { |
944 | result = KAUTH_RESULT_DEFER; |
945 | break; |
946 | } |
947 | |
948 | vp = arg1; |
949 | KASSERT(vp != NULL); |
950 | |
951 | /* Handle /dev/mem and /dev/kmem. */ |
952 | if (iskmemvp(vp)) { |
953 | if (veriexec_strict < VERIEXEC_IPS) |
954 | result = KAUTH_RESULT_DEFER; |
955 | |
956 | break; |
957 | } |
958 | |
959 | error = rawdev_mounted(vp, &bvp); |
960 | if (error == EINVAL) { |
961 | result = KAUTH_RESULT_DEFER; |
962 | break; |
963 | } |
964 | |
965 | /* |
966 | * XXX: See vfs_mountedon() comment in rawdev_mounted(). |
967 | */ |
968 | vte = veriexec_table_lookup(bvp->v_mount); |
969 | if (vte == NULL) { |
970 | result = KAUTH_RESULT_DEFER; |
971 | break; |
972 | } |
973 | |
974 | switch (veriexec_strict) { |
975 | case VERIEXEC_LEARNING: |
976 | case VERIEXEC_IDS: |
977 | result = KAUTH_RESULT_DEFER; |
978 | |
979 | rw_enter(&veriexec_op_lock, RW_WRITER); |
980 | fileassoc_table_run(bvp->v_mount, veriexec_hook, |
981 | (fileassoc_cb_t)veriexec_file_purge_cb, NULL); |
982 | rw_exit(&veriexec_op_lock); |
983 | |
984 | break; |
985 | case VERIEXEC_IPS: |
986 | result = KAUTH_RESULT_DENY; |
987 | break; |
988 | case VERIEXEC_LOCKDOWN: |
989 | result = KAUTH_RESULT_DENY; |
990 | break; |
991 | } |
992 | |
993 | break; |
994 | } |
995 | |
996 | case KAUTH_DEVICE_RAWIO_PASSTHRU: |
997 | /* XXX What can we do here? */ |
998 | if (veriexec_strict < VERIEXEC_IPS) |
999 | result = KAUTH_RESULT_DEFER; |
1000 | |
1001 | break; |
1002 | |
1003 | default: |
1004 | result = KAUTH_RESULT_DEFER; |
1005 | break; |
1006 | } |
1007 | |
1008 | return (result); |
1009 | } |
1010 | |
1011 | /* |
1012 | * Create a new Veriexec table. |
1013 | */ |
1014 | static struct veriexec_table_entry * |
1015 | veriexec_table_add(struct lwp *l, struct mount *mp) |
1016 | { |
1017 | struct veriexec_table_entry *vte; |
1018 | u_char buf[16]; |
1019 | |
1020 | vte = kmem_zalloc(sizeof(*vte), KM_SLEEP); |
1021 | mount_setspecific(mp, veriexec_mountspecific_key, vte); |
1022 | |
1023 | snprintf(buf, sizeof(buf), "table%u" , veriexec_tablecount++); |
1024 | sysctl_createv(NULL, 0, &veriexec_count_node, &vte->vte_node, |
1025 | 0, CTLTYPE_NODE, buf, NULL, NULL, 0, NULL, |
1026 | 0, CTL_CREATE, CTL_EOL); |
1027 | |
1028 | sysctl_createv(NULL, 0, &vte->vte_node, NULL, |
1029 | CTLFLAG_READONLY, CTLTYPE_STRING, "mntpt" , |
1030 | NULL, NULL, 0, mp->mnt_stat.f_mntonname, |
1031 | 0, CTL_CREATE, CTL_EOL); |
1032 | sysctl_createv(NULL, 0, &vte->vte_node, NULL, |
1033 | CTLFLAG_READONLY, CTLTYPE_STRING, "fstype" , |
1034 | NULL, NULL, 0, mp->mnt_stat.f_fstypename, |
1035 | 0, CTL_CREATE, CTL_EOL); |
1036 | sysctl_createv(NULL, 0, &vte->vte_node, NULL, |
1037 | CTLFLAG_READONLY, CTLTYPE_QUAD, "nentries" , |
1038 | NULL, NULL, 0, &vte->vte_count, 0, CTL_CREATE, CTL_EOL); |
1039 | |
1040 | return (vte); |
1041 | } |
1042 | |
1043 | /* |
1044 | * Add a file to be monitored by Veriexec. |
1045 | * |
1046 | * Expected elements in dict: file, fp, fp-type, entry-type. |
1047 | */ |
1048 | int |
1049 | veriexec_file_add(struct lwp *l, prop_dictionary_t dict) |
1050 | { |
1051 | struct veriexec_table_entry *vte; |
1052 | struct veriexec_file_entry *vfe = NULL; |
1053 | struct vnode *vp; |
1054 | const char *file, *fp_type; |
1055 | int error; |
1056 | |
1057 | if (!prop_dictionary_get_cstring_nocopy(dict, "file" , &file)) |
1058 | return (EINVAL); |
1059 | |
1060 | error = namei_simple_kernel(file, NSM_FOLLOW_NOEMULROOT, &vp); |
1061 | if (error) |
1062 | return (error); |
1063 | |
1064 | /* Add only regular files. */ |
1065 | if (vp->v_type != VREG) { |
1066 | log(LOG_ERR, "Veriexec: Not adding `%s': Not a regular file.\n" , |
1067 | file); |
1068 | error = EBADF; |
1069 | goto out; |
1070 | } |
1071 | |
1072 | vfe = kmem_zalloc(sizeof(*vfe), KM_SLEEP); |
1073 | rw_init(&vfe->lock); |
1074 | |
1075 | /* Lookup fingerprint hashing algorithm. */ |
1076 | fp_type = prop_string_cstring_nocopy(prop_dictionary_get(dict, |
1077 | "fp-type" )); |
1078 | if ((vfe->ops = veriexec_fpops_lookup(fp_type)) == NULL) { |
1079 | log(LOG_ERR, "Veriexec: Invalid or unknown fingerprint type " |
1080 | "`%s' for file `%s'.\n" , fp_type, file); |
1081 | error = EOPNOTSUPP; |
1082 | goto out; |
1083 | } |
1084 | |
1085 | if (prop_data_size(prop_dictionary_get(dict, "fp" )) != |
1086 | vfe->ops->hash_len) { |
1087 | log(LOG_ERR, "Veriexec: Bad fingerprint length for `%s'.\n" , |
1088 | file); |
1089 | error = EINVAL; |
1090 | goto out; |
1091 | } |
1092 | |
1093 | vfe->fp = kmem_alloc(vfe->ops->hash_len, KM_SLEEP); |
1094 | memcpy(vfe->fp, prop_data_data_nocopy(prop_dictionary_get(dict, "fp" )), |
1095 | vfe->ops->hash_len); |
1096 | |
1097 | rw_enter(&veriexec_op_lock, RW_WRITER); |
1098 | |
1099 | if (veriexec_get(vp)) { |
1100 | /* We already have an entry for this file. */ |
1101 | error = EEXIST; |
1102 | goto unlock_out; |
1103 | } |
1104 | |
1105 | /* Continue entry initialization. */ |
1106 | if (prop_dictionary_get_uint8(dict, "entry-type" , &vfe->type) == FALSE) |
1107 | vfe->type = 0; |
1108 | else { |
1109 | uint8_t ; |
1110 | |
1111 | extra_flags = vfe->type & ~(VERIEXEC_DIRECT | |
1112 | VERIEXEC_INDIRECT | VERIEXEC_FILE | VERIEXEC_UNTRUSTED); |
1113 | if (extra_flags) { |
1114 | log(LOG_NOTICE, "Veriexec: Contaminated flags `0x%x' " |
1115 | "for `%s', skipping.\n" , extra_flags, file); |
1116 | error = EINVAL; |
1117 | goto unlock_out; |
1118 | } |
1119 | } |
1120 | if (!(vfe->type & (VERIEXEC_DIRECT | VERIEXEC_INDIRECT | |
1121 | VERIEXEC_FILE))) |
1122 | vfe->type |= VERIEXEC_DIRECT; |
1123 | |
1124 | vfe->status = FINGERPRINT_NOTEVAL; |
1125 | if (prop_bool_true(prop_dictionary_get(dict, "keep-filename" ))) { |
1126 | vfe->filename_len = strlen(file) + 1; |
1127 | vfe->filename = kmem_alloc(vfe->filename_len, KM_SLEEP); |
1128 | strlcpy(vfe->filename, file, vfe->filename_len); |
1129 | } else |
1130 | vfe->filename = NULL; |
1131 | |
1132 | if (prop_bool_true(prop_dictionary_get(dict, "eval-on-load" )) || |
1133 | (vfe->type & VERIEXEC_UNTRUSTED)) { |
1134 | u_char status; |
1135 | |
1136 | error = veriexec_fp_status(l, vp, VERIEXEC_FILE_UNLOCKED, |
1137 | vfe, &status); |
1138 | if (error) |
1139 | goto unlock_out; |
1140 | vfe->status = status; |
1141 | } |
1142 | |
1143 | vte = veriexec_table_lookup(vp->v_mount); |
1144 | if (vte == NULL) |
1145 | vte = veriexec_table_add(l, vp->v_mount); |
1146 | |
1147 | /* XXX if we bail below this, we might want to gc newly created vtes. */ |
1148 | |
1149 | error = fileassoc_add(vp, veriexec_hook, vfe); |
1150 | if (error) |
1151 | goto unlock_out; |
1152 | |
1153 | vte->vte_count++; |
1154 | |
1155 | veriexec_file_report(NULL, "New entry." , file, NULL, REPORT_DEBUG); |
1156 | veriexec_bypass = 0; |
1157 | |
1158 | unlock_out: |
1159 | rw_exit(&veriexec_op_lock); |
1160 | |
1161 | out: |
1162 | vrele(vp); |
1163 | if (error) |
1164 | veriexec_file_free(vfe); |
1165 | |
1166 | return (error); |
1167 | } |
1168 | |
1169 | int |
1170 | veriexec_table_delete(struct lwp *l, struct mount *mp) |
1171 | { |
1172 | struct veriexec_table_entry *vte; |
1173 | |
1174 | vte = veriexec_table_lookup(mp); |
1175 | if (vte == NULL) |
1176 | return (ENOENT); |
1177 | |
1178 | veriexec_mountspecific_dtor(vte); |
1179 | mount_setspecific(mp, veriexec_mountspecific_key, NULL); |
1180 | |
1181 | return (fileassoc_table_clear(mp, veriexec_hook)); |
1182 | } |
1183 | |
1184 | int |
1185 | veriexec_file_delete(struct lwp *l, struct vnode *vp) |
1186 | { |
1187 | struct veriexec_table_entry *vte; |
1188 | int error; |
1189 | |
1190 | vte = veriexec_table_lookup(vp->v_mount); |
1191 | if (vte == NULL) |
1192 | return (ENOENT); |
1193 | |
1194 | rw_enter(&veriexec_op_lock, RW_WRITER); |
1195 | error = fileassoc_clear(vp, veriexec_hook); |
1196 | rw_exit(&veriexec_op_lock); |
1197 | if (!error) { |
1198 | KASSERT(vte->vte_count > 0); |
1199 | vte->vte_count--; |
1200 | } |
1201 | |
1202 | return (error); |
1203 | } |
1204 | |
1205 | /* |
1206 | * Convert Veriexec entry data to a dictionary readable by userland tools. |
1207 | */ |
1208 | static void |
1209 | veriexec_file_convert(struct veriexec_file_entry *vfe, prop_dictionary_t rdict) |
1210 | { |
1211 | if (vfe->filename) |
1212 | prop_dictionary_set(rdict, "file" , |
1213 | prop_string_create_cstring(vfe->filename)); |
1214 | prop_dictionary_set_uint8(rdict, "entry-type" , vfe->type); |
1215 | prop_dictionary_set_uint8(rdict, "status" , vfe->status); |
1216 | prop_dictionary_set(rdict, "fp-type" , |
1217 | prop_string_create_cstring(vfe->ops->type)); |
1218 | prop_dictionary_set(rdict, "fp" , |
1219 | prop_data_create_data(vfe->fp, vfe->ops->hash_len)); |
1220 | } |
1221 | |
1222 | int |
1223 | veriexec_convert(struct vnode *vp, prop_dictionary_t rdict) |
1224 | { |
1225 | struct veriexec_file_entry *vfe; |
1226 | |
1227 | rw_enter(&veriexec_op_lock, RW_READER); |
1228 | |
1229 | vfe = veriexec_get(vp); |
1230 | if (vfe == NULL) { |
1231 | rw_exit(&veriexec_op_lock); |
1232 | return (ENOENT); |
1233 | } |
1234 | |
1235 | rw_enter(&vfe->lock, RW_READER); |
1236 | veriexec_file_convert(vfe, rdict); |
1237 | rw_exit(&vfe->lock); |
1238 | |
1239 | rw_exit(&veriexec_op_lock); |
1240 | return (0); |
1241 | } |
1242 | |
1243 | int |
1244 | veriexec_unmountchk(struct mount *mp) |
1245 | { |
1246 | int error; |
1247 | |
1248 | if ((veriexec_bypass && (veriexec_strict == VERIEXEC_LEARNING)) |
1249 | || doing_shutdown) |
1250 | return (0); |
1251 | |
1252 | rw_enter(&veriexec_op_lock, RW_READER); |
1253 | |
1254 | switch (veriexec_strict) { |
1255 | case VERIEXEC_LEARNING: |
1256 | error = 0; |
1257 | break; |
1258 | |
1259 | case VERIEXEC_IDS: |
1260 | if (veriexec_table_lookup(mp) != NULL) { |
1261 | log(LOG_INFO, "Veriexec: IDS mode, allowing unmount " |
1262 | "of \"%s\".\n" , mp->mnt_stat.f_mntonname); |
1263 | } |
1264 | |
1265 | error = 0; |
1266 | break; |
1267 | |
1268 | case VERIEXEC_IPS: { |
1269 | struct veriexec_table_entry *vte; |
1270 | |
1271 | vte = veriexec_table_lookup(mp); |
1272 | if ((vte != NULL) && (vte->vte_count > 0)) { |
1273 | log(LOG_ALERT, "Veriexec: IPS mode, preventing" |
1274 | " unmount of \"%s\" with monitored files.\n" , |
1275 | mp->mnt_stat.f_mntonname); |
1276 | |
1277 | error = EPERM; |
1278 | } else |
1279 | error = 0; |
1280 | break; |
1281 | } |
1282 | |
1283 | case VERIEXEC_LOCKDOWN: |
1284 | default: |
1285 | log(LOG_ALERT, "Veriexec: Lockdown mode, preventing unmount " |
1286 | "of \"%s\".\n" , mp->mnt_stat.f_mntonname); |
1287 | error = EPERM; |
1288 | break; |
1289 | } |
1290 | |
1291 | rw_exit(&veriexec_op_lock); |
1292 | return (error); |
1293 | } |
1294 | |
1295 | int |
1296 | veriexec_openchk(struct lwp *l, struct vnode *vp, const char *path, int fmode) |
1297 | { |
1298 | struct veriexec_file_entry *vfe = NULL; |
1299 | int error = 0; |
1300 | |
1301 | if (veriexec_bypass && (veriexec_strict == VERIEXEC_LEARNING)) |
1302 | return 0; |
1303 | |
1304 | if (vp == NULL) { |
1305 | /* If no creation requested, let this fail normally. */ |
1306 | if (!(fmode & O_CREAT)) |
1307 | goto out; |
1308 | |
1309 | /* Lockdown mode: Prevent creation of new files. */ |
1310 | if (veriexec_strict >= VERIEXEC_LOCKDOWN) { |
1311 | log(LOG_ALERT, "Veriexec: Preventing new file " |
1312 | "creation in `%s'.\n" , path); |
1313 | error = EPERM; |
1314 | } |
1315 | |
1316 | goto out; |
1317 | } |
1318 | |
1319 | rw_enter(&veriexec_op_lock, RW_READER); |
1320 | error = veriexec_file_verify(l, vp, path, VERIEXEC_FILE, |
1321 | VERIEXEC_FILE_LOCKED, &vfe); |
1322 | |
1323 | if (error) { |
1324 | rw_exit(&veriexec_op_lock); |
1325 | goto out; |
1326 | } |
1327 | |
1328 | if ((vfe != NULL) && ((fmode & FWRITE) || (fmode & O_TRUNC))) { |
1329 | veriexec_file_report(vfe, "Write access request." , path, l, |
1330 | REPORT_ALWAYS | REPORT_ALARM); |
1331 | |
1332 | /* IPS mode: Deny write access to monitored files. */ |
1333 | if (veriexec_strict >= VERIEXEC_IPS) |
1334 | error = EPERM; |
1335 | else |
1336 | veriexec_file_purge(vfe, VERIEXEC_LOCKED); |
1337 | } |
1338 | |
1339 | if (vfe != NULL) |
1340 | rw_exit(&vfe->lock); |
1341 | |
1342 | rw_exit(&veriexec_op_lock); |
1343 | out: |
1344 | return (error); |
1345 | } |
1346 | |
1347 | static void |
1348 | veriexec_file_dump(struct veriexec_file_entry *vfe, prop_array_t entries) |
1349 | { |
1350 | prop_dictionary_t entry; |
1351 | |
1352 | /* If we don't have a filename, this is meaningless. */ |
1353 | if (vfe->filename == NULL) |
1354 | return; |
1355 | |
1356 | entry = prop_dictionary_create(); |
1357 | |
1358 | veriexec_file_convert(vfe, entry); |
1359 | |
1360 | prop_array_add(entries, entry); |
1361 | } |
1362 | |
1363 | int |
1364 | veriexec_dump(struct lwp *l, prop_array_t rarray) |
1365 | { |
1366 | struct mount *mp, *nmp; |
1367 | |
1368 | mutex_enter(&mountlist_lock); |
1369 | for (mp = TAILQ_FIRST(&mountlist); mp != NULL; mp = nmp) { |
1370 | /* If it fails, the file-system is [being] unmounted. */ |
1371 | if (vfs_busy(mp, &nmp) != 0) |
1372 | continue; |
1373 | |
1374 | fileassoc_table_run(mp, veriexec_hook, |
1375 | (fileassoc_cb_t)veriexec_file_dump, rarray); |
1376 | |
1377 | vfs_unbusy(mp, false, &nmp); |
1378 | } |
1379 | mutex_exit(&mountlist_lock); |
1380 | |
1381 | return (0); |
1382 | } |
1383 | |
1384 | int |
1385 | veriexec_flush(struct lwp *l) |
1386 | { |
1387 | struct mount *mp, *nmp; |
1388 | int error = 0; |
1389 | |
1390 | mutex_enter(&mountlist_lock); |
1391 | for (mp = TAILQ_FIRST(&mountlist); mp != NULL; mp = nmp) { |
1392 | int lerror; |
1393 | |
1394 | /* If it fails, the file-system is [being] unmounted. */ |
1395 | if (vfs_busy(mp, &nmp) != 0) |
1396 | continue; |
1397 | |
1398 | lerror = veriexec_table_delete(l, mp); |
1399 | if (lerror && lerror != ENOENT) |
1400 | error = lerror; |
1401 | |
1402 | vfs_unbusy(mp, false, &nmp); |
1403 | } |
1404 | mutex_exit(&mountlist_lock); |
1405 | |
1406 | return (error); |
1407 | } |
1408 | |