]>
Commit | Line | Data |
---|---|---|
39236c6e | 1 | /* |
d9a64523 | 2 | * Copyright (c) 2018 Apple Inc. All rights reserved. |
39236c6e A |
3 | * |
4 | * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ | |
0a7de745 | 5 | * |
39236c6e A |
6 | * This file contains Original Code and/or Modifications of Original Code |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. The rights granted to you under the License | |
10 | * may not be used to create, or enable the creation or redistribution of, | |
11 | * unlawful or unlicensed copies of an Apple operating system, or to | |
12 | * circumvent, violate, or enable the circumvention or violation of, any | |
13 | * terms of an Apple operating system software license agreement. | |
0a7de745 | 14 | * |
39236c6e A |
15 | * Please obtain a copy of the License at |
16 | * http://www.opensource.apple.com/apsl/ and read it before using this file. | |
0a7de745 | 17 | * |
39236c6e A |
18 | * The Original Code and all software distributed under the License are |
19 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
20 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
21 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
22 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
23 | * Please see the License for the specific language governing rights and | |
24 | * limitations under the License. | |
0a7de745 | 25 | * |
39236c6e A |
26 | * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ |
27 | */ | |
28 | ||
29 | #include <sys/param.h> | |
30 | #include <sys/systm.h> | |
31 | #include <sys/filedesc.h> | |
32 | #include <sys/kernel.h> | |
33 | #include <sys/file_internal.h> | |
5ba3f43e | 34 | #include <kern/exc_guard.h> |
39236c6e A |
35 | #include <sys/guarded.h> |
36 | #include <kern/kalloc.h> | |
37 | #include <sys/sysproto.h> | |
38 | #include <sys/vnode.h> | |
fe8ab488 A |
39 | #include <sys/vnode_internal.h> |
40 | #include <sys/uio_internal.h> | |
41 | #include <sys/ubc_internal.h> | |
39236c6e A |
42 | #include <vfs/vfs_support.h> |
43 | #include <security/audit/audit.h> | |
fe8ab488 A |
44 | #include <sys/syscall.h> |
45 | #include <sys/kauth.h> | |
46 | #include <sys/kdebug.h> | |
47 | #include <stdbool.h> | |
48 | #include <vm/vm_protos.h> | |
5ba3f43e A |
49 | #include <libkern/section_keywords.h> |
50 | #if CONFIG_MACF && CONFIG_VNGUARD | |
51 | #include <security/mac.h> | |
52 | #include <security/mac_framework.h> | |
53 | #include <security/mac_policy.h> | |
54 | #include <pexpert/pexpert.h> | |
55 | #include <sys/sysctl.h> | |
d9a64523 | 56 | #include <sys/reason.h> |
5ba3f43e | 57 | #endif |
fe8ab488 A |
58 | |
59 | ||
f427ee49 | 60 | #define f_flag fp_glob->fg_flag |
fe8ab488 | 61 | extern int dofilewrite(vfs_context_t ctx, struct fileproc *fp, |
0a7de745 A |
62 | user_addr_t bufp, user_size_t nbyte, off_t offset, |
63 | int flags, user_ssize_t *retval ); | |
f427ee49 | 64 | extern int do_uiowrite(struct proc *p, struct fileproc *fp, uio_t uio, int flags, user_ssize_t *retval); |
39236c6e A |
65 | |
66 | /* | |
67 | * Experimental guarded file descriptor support. | |
68 | */ | |
69 | ||
70 | kern_return_t task_exception_notify(exception_type_t exception, | |
0a7de745 | 71 | mach_exception_data_type_t code, mach_exception_data_type_t subcode); |
5ba3f43e | 72 | kern_return_t task_violated_guard(mach_exception_code_t, mach_exception_subcode_t, void *); |
39236c6e A |
73 | |
74 | /* | |
75 | * Most fd's have an underlying fileproc struct; but some may be | |
76 | * guarded_fileproc structs which implement guarded fds. The latter | |
77 | * struct (below) embeds the former. | |
78 | * | |
f427ee49 | 79 | * The two types should be distinguished by the "type" portion of fp_flags. |
39236c6e A |
80 | * There's also a magic number to help catch misuse and bugs. |
81 | * | |
82 | * This is a bit unpleasant, but results from the desire to allow | |
83 | * alternate file behaviours for a few file descriptors without | |
84 | * growing the fileproc data structure. | |
85 | */ | |
86 | ||
87 | struct guarded_fileproc { | |
88 | struct fileproc gf_fileproc; | |
0a7de745 A |
89 | u_int gf_magic; |
90 | u_int gf_attrs; | |
91 | guardid_t gf_guard; | |
39236c6e A |
92 | }; |
93 | ||
0a7de745 | 94 | const size_t sizeof_guarded_fileproc = sizeof(struct guarded_fileproc); |
39236c6e | 95 | |
0a7de745 A |
96 | #define FP_TO_GFP(fp) ((struct guarded_fileproc *)(fp)) |
97 | #define GFP_TO_FP(gfp) (&(gfp)->gf_fileproc) | |
39236c6e | 98 | |
0a7de745 | 99 | #define GUARDED_FILEPROC_MAGIC 0x29083 |
39236c6e A |
100 | |
101 | struct gfp_crarg { | |
102 | guardid_t gca_guard; | |
103 | u_int gca_attrs; | |
104 | }; | |
105 | ||
106 | static struct fileproc * | |
107 | guarded_fileproc_alloc_init(void *crarg) | |
108 | { | |
109 | struct gfp_crarg *aarg = crarg; | |
110 | struct guarded_fileproc *gfp; | |
111 | ||
0a7de745 A |
112 | if ((gfp = kalloc(sizeof(*gfp))) == NULL) { |
113 | return NULL; | |
114 | } | |
39236c6e | 115 | |
0a7de745 | 116 | bzero(gfp, sizeof(*gfp)); |
cb323159 A |
117 | |
118 | struct fileproc *fp = &gfp->gf_fileproc; | |
f427ee49 A |
119 | os_ref_init(&fp->fp_iocount, &f_refgrp); |
120 | fp->fp_flags = FTYPE_GUARDED; | |
cb323159 | 121 | |
39236c6e A |
122 | gfp->gf_magic = GUARDED_FILEPROC_MAGIC; |
123 | gfp->gf_guard = aarg->gca_guard; | |
124 | gfp->gf_attrs = aarg->gca_attrs; | |
125 | ||
0a7de745 | 126 | return GFP_TO_FP(gfp); |
39236c6e A |
127 | } |
128 | ||
129 | void | |
130 | guarded_fileproc_free(struct fileproc *fp) | |
131 | { | |
132 | struct guarded_fileproc *gfp = FP_TO_GFP(fp); | |
133 | ||
134 | if (FILEPROC_TYPE(fp) != FTYPE_GUARDED || | |
0a7de745 | 135 | GUARDED_FILEPROC_MAGIC != gfp->gf_magic) { |
f427ee49 | 136 | panic("%s: corrupt fp %p flags %x", __func__, fp, fp->fp_flags); |
0a7de745 | 137 | } |
39236c6e | 138 | |
0a7de745 | 139 | kfree(gfp, sizeof(*gfp)); |
39236c6e A |
140 | } |
141 | ||
142 | static int | |
143 | fp_lookup_guarded(proc_t p, int fd, guardid_t guard, | |
3e170ce0 | 144 | struct guarded_fileproc **gfpp, int locked) |
39236c6e A |
145 | { |
146 | struct fileproc *fp; | |
147 | int error; | |
148 | ||
0a7de745 A |
149 | if ((error = fp_lookup(p, fd, &fp, locked)) != 0) { |
150 | return error; | |
151 | } | |
39236c6e | 152 | if (FILEPROC_TYPE(fp) != FTYPE_GUARDED) { |
3e170ce0 | 153 | (void) fp_drop(p, fd, fp, locked); |
0a7de745 | 154 | return EINVAL; |
39236c6e A |
155 | } |
156 | struct guarded_fileproc *gfp = FP_TO_GFP(fp); | |
157 | ||
0a7de745 | 158 | if (GUARDED_FILEPROC_MAGIC != gfp->gf_magic) { |
39236c6e | 159 | panic("%s: corrupt fp %p", __func__, fp); |
0a7de745 | 160 | } |
39236c6e A |
161 | |
162 | if (guard != gfp->gf_guard) { | |
3e170ce0 | 163 | (void) fp_drop(p, fd, fp, locked); |
0a7de745 | 164 | return EPERM; /* *not* a mismatch exception */ |
39236c6e | 165 | } |
0a7de745 | 166 | if (gfpp) { |
39236c6e | 167 | *gfpp = gfp; |
0a7de745 A |
168 | } |
169 | return 0; | |
39236c6e A |
170 | } |
171 | ||
172 | /* | |
173 | * Expected use pattern: | |
174 | * | |
175 | * if (FP_ISGUARDED(fp, GUARD_CLOSE)) { | |
0a7de745 | 176 | * error = fp_guard_exception(p, fd, fp, kGUARD_EXC_CLOSE); |
39236c6e | 177 | * proc_fdunlock(p); |
cb323159 | 178 | * return error; |
39236c6e A |
179 | * } |
180 | */ | |
181 | ||
182 | int | |
183 | fp_isguarded(struct fileproc *fp, u_int attrs) | |
184 | { | |
185 | if (FILEPROC_TYPE(fp) == FTYPE_GUARDED) { | |
186 | struct guarded_fileproc *gfp = FP_TO_GFP(fp); | |
187 | ||
0a7de745 | 188 | if (GUARDED_FILEPROC_MAGIC != gfp->gf_magic) { |
39236c6e | 189 | panic("%s: corrupt gfp %p flags %x", |
f427ee49 | 190 | __func__, gfp, fp->fp_flags); |
0a7de745 A |
191 | } |
192 | return (attrs & gfp->gf_attrs) == attrs; | |
39236c6e | 193 | } |
0a7de745 | 194 | return 0; |
39236c6e A |
195 | } |
196 | ||
197 | extern char *proc_name_address(void *p); | |
198 | ||
199 | int | |
5ba3f43e | 200 | fp_guard_exception(proc_t p, int fd, struct fileproc *fp, u_int flavor) |
39236c6e | 201 | { |
0a7de745 | 202 | if (FILEPROC_TYPE(fp) != FTYPE_GUARDED) { |
f427ee49 | 203 | panic("%s corrupt fp %p flags %x", __func__, fp, fp->fp_flags); |
0a7de745 | 204 | } |
39236c6e A |
205 | |
206 | struct guarded_fileproc *gfp = FP_TO_GFP(fp); | |
39236c6e A |
207 | /* all gfd fields protected via proc_fdlock() */ |
208 | proc_fdlock_assert(p, LCK_MTX_ASSERT_OWNED); | |
209 | ||
5ba3f43e A |
210 | mach_exception_code_t code = 0; |
211 | EXC_GUARD_ENCODE_TYPE(code, GUARD_TYPE_FD); | |
212 | EXC_GUARD_ENCODE_FLAVOR(code, flavor); | |
213 | EXC_GUARD_ENCODE_TARGET(code, fd); | |
214 | mach_exception_subcode_t subcode = gfp->gf_guard; | |
39236c6e | 215 | |
5ba3f43e | 216 | thread_t t = current_thread(); |
cb323159 | 217 | thread_guard_violation(t, code, subcode, TRUE); |
0a7de745 | 218 | return EPERM; |
39236c6e A |
219 | } |
220 | ||
221 | /* | |
222 | * (Invoked before returning to userland from the syscall handler.) | |
223 | */ | |
224 | void | |
5ba3f43e A |
225 | fd_guard_ast( |
226 | thread_t __unused t, | |
227 | mach_exception_code_t code, | |
228 | mach_exception_subcode_t subcode) | |
39236c6e | 229 | { |
5ba3f43e | 230 | task_exception_notify(EXC_GUARD, code, subcode); |
39236c6e | 231 | proc_t p = current_proc(); |
5ba3f43e | 232 | psignal(p, SIGKILL); |
39236c6e A |
233 | } |
234 | ||
235 | /* | |
236 | * Experimental guarded file descriptor SPIs | |
237 | */ | |
238 | ||
239 | /* | |
240 | * int guarded_open_np(const char *pathname, int flags, | |
241 | * const guardid_t *guard, u_int guardflags, ...); | |
242 | * | |
243 | * In this initial implementation, GUARD_DUP must be specified. | |
244 | * GUARD_CLOSE, GUARD_SOCKET_IPC and GUARD_FILEPORT are optional. | |
245 | * | |
246 | * If GUARD_DUP wasn't specified, then we'd have to do the (extra) work | |
247 | * to allow dup-ing a descriptor to inherit the guard onto the new | |
248 | * descriptor. (Perhaps GUARD_DUP behaviours should just always be true | |
249 | * for a guarded fd? Or, more sanely, all the dup operations should | |
250 | * just always propagate the guard?) | |
251 | * | |
252 | * Guarded descriptors are always close-on-exec, and GUARD_CLOSE | |
253 | * requires close-on-fork; O_CLOEXEC must be set in flags. | |
254 | * This setting is immutable; attempts to clear the flag will | |
255 | * cause a guard exception. | |
3e170ce0 A |
256 | * |
257 | * XXX It's somewhat broken that change_fdguard_np() can completely | |
258 | * remove the guard and thus revoke down the immutability | |
259 | * promises above. Ick. | |
39236c6e A |
260 | */ |
261 | int | |
262 | guarded_open_np(proc_t p, struct guarded_open_np_args *uap, int32_t *retval) | |
263 | { | |
0a7de745 A |
264 | if ((uap->flags & O_CLOEXEC) == 0) { |
265 | return EINVAL; | |
266 | } | |
39236c6e A |
267 | |
268 | #define GUARD_REQUIRED (GUARD_DUP) | |
0a7de745 A |
269 | #define GUARD_ALL (GUARD_REQUIRED | \ |
270 | (GUARD_CLOSE | GUARD_SOCKET_IPC | GUARD_FILEPORT | GUARD_WRITE)) | |
39236c6e A |
271 | |
272 | if (((uap->guardflags & GUARD_REQUIRED) != GUARD_REQUIRED) || | |
0a7de745 A |
273 | ((uap->guardflags & ~GUARD_ALL) != 0)) { |
274 | return EINVAL; | |
275 | } | |
39236c6e A |
276 | |
277 | int error; | |
278 | struct gfp_crarg crarg = { | |
279 | .gca_attrs = uap->guardflags | |
280 | }; | |
281 | ||
282 | if ((error = copyin(uap->guard, | |
0a7de745 A |
283 | &(crarg.gca_guard), sizeof(crarg.gca_guard))) != 0) { |
284 | return error; | |
285 | } | |
39236c6e A |
286 | |
287 | /* | |
288 | * Disallow certain guard values -- is zero enough? | |
289 | */ | |
0a7de745 A |
290 | if (crarg.gca_guard == 0) { |
291 | return EINVAL; | |
292 | } | |
39236c6e A |
293 | |
294 | struct filedesc *fdp = p->p_fd; | |
295 | struct vnode_attr va; | |
296 | struct nameidata nd; | |
297 | vfs_context_t ctx = vfs_context_current(); | |
298 | int cmode; | |
299 | ||
300 | VATTR_INIT(&va); | |
301 | cmode = ((uap->mode & ~fdp->fd_cmask) & ALLPERMS) & ~S_ISTXT; | |
302 | VATTR_SET(&va, va_mode, cmode & ACCESSPERMS); | |
303 | ||
304 | NDINIT(&nd, LOOKUP, OP_OPEN, FOLLOW | AUDITVNPATH1, UIO_USERSPACE, | |
0a7de745 | 305 | uap->path, ctx); |
39236c6e | 306 | |
0a7de745 A |
307 | return open1(ctx, &nd, uap->flags | O_CLOFORK, &va, |
308 | guarded_fileproc_alloc_init, &crarg, retval); | |
39236c6e A |
309 | } |
310 | ||
fe8ab488 A |
311 | /* |
312 | * int guarded_open_dprotected_np(const char *pathname, int flags, | |
313 | * const guardid_t *guard, u_int guardflags, int dpclass, int dpflags, ...); | |
314 | * | |
315 | * This SPI is extension of guarded_open_np() to include dataprotection class on creation | |
316 | * in "dpclass" and dataprotection flags 'dpflags'. Otherwise behaviors are same as in | |
317 | * guarded_open_np() | |
318 | */ | |
319 | int | |
320 | guarded_open_dprotected_np(proc_t p, struct guarded_open_dprotected_np_args *uap, int32_t *retval) | |
321 | { | |
0a7de745 A |
322 | if ((uap->flags & O_CLOEXEC) == 0) { |
323 | return EINVAL; | |
324 | } | |
fe8ab488 | 325 | |
fe8ab488 | 326 | if (((uap->guardflags & GUARD_REQUIRED) != GUARD_REQUIRED) || |
0a7de745 A |
327 | ((uap->guardflags & ~GUARD_ALL) != 0)) { |
328 | return EINVAL; | |
329 | } | |
fe8ab488 A |
330 | |
331 | int error; | |
332 | struct gfp_crarg crarg = { | |
333 | .gca_attrs = uap->guardflags | |
334 | }; | |
335 | ||
336 | if ((error = copyin(uap->guard, | |
0a7de745 A |
337 | &(crarg.gca_guard), sizeof(crarg.gca_guard))) != 0) { |
338 | return error; | |
339 | } | |
fe8ab488 A |
340 | |
341 | /* | |
342 | * Disallow certain guard values -- is zero enough? | |
343 | */ | |
0a7de745 A |
344 | if (crarg.gca_guard == 0) { |
345 | return EINVAL; | |
346 | } | |
fe8ab488 A |
347 | |
348 | struct filedesc *fdp = p->p_fd; | |
349 | struct vnode_attr va; | |
350 | struct nameidata nd; | |
351 | vfs_context_t ctx = vfs_context_current(); | |
352 | int cmode; | |
353 | ||
354 | VATTR_INIT(&va); | |
355 | cmode = ((uap->mode & ~fdp->fd_cmask) & ALLPERMS) & ~S_ISTXT; | |
356 | VATTR_SET(&va, va_mode, cmode & ACCESSPERMS); | |
357 | ||
358 | NDINIT(&nd, LOOKUP, OP_OPEN, FOLLOW | AUDITVNPATH1, UIO_USERSPACE, | |
0a7de745 | 359 | uap->path, ctx); |
fe8ab488 | 360 | |
0a7de745 A |
361 | /* |
362 | * Initialize the extra fields in vnode_attr to pass down dataprotection | |
fe8ab488 A |
363 | * extra fields. |
364 | * 1. target cprotect class. | |
0a7de745 A |
365 | * 2. set a flag to mark it as requiring open-raw-encrypted semantics. |
366 | */ | |
367 | if (uap->flags & O_CREAT) { | |
fe8ab488 A |
368 | VATTR_SET(&va, va_dataprotect_class, uap->dpclass); |
369 | } | |
0a7de745 A |
370 | |
371 | if (uap->dpflags & (O_DP_GETRAWENCRYPTED | O_DP_GETRAWUNENCRYPTED)) { | |
372 | if (uap->flags & (O_RDWR | O_WRONLY)) { | |
fe8ab488 | 373 | /* Not allowed to write raw encrypted bytes */ |
0a7de745 A |
374 | return EINVAL; |
375 | } | |
3e170ce0 | 376 | if (uap->dpflags & O_DP_GETRAWENCRYPTED) { |
0a7de745 | 377 | VATTR_SET(&va, va_dataprotect_flags, VA_DP_RAWENCRYPTED); |
3e170ce0 A |
378 | } |
379 | if (uap->dpflags & O_DP_GETRAWUNENCRYPTED) { | |
0a7de745 | 380 | VATTR_SET(&va, va_dataprotect_flags, VA_DP_RAWUNENCRYPTED); |
3e170ce0 | 381 | } |
fe8ab488 A |
382 | } |
383 | ||
0a7de745 A |
384 | return open1(ctx, &nd, uap->flags | O_CLOFORK, &va, |
385 | guarded_fileproc_alloc_init, &crarg, retval); | |
fe8ab488 A |
386 | } |
387 | ||
39236c6e A |
388 | /* |
389 | * int guarded_kqueue_np(const guardid_t *guard, u_int guardflags); | |
390 | * | |
391 | * Create a guarded kqueue descriptor with guardid and guardflags. | |
392 | * | |
393 | * Same restrictions on guardflags as for guarded_open_np(). | |
3e170ce0 A |
394 | * All kqueues are -always- close-on-exec and close-on-fork by themselves |
395 | * and are not sendable. | |
39236c6e A |
396 | */ |
397 | int | |
398 | guarded_kqueue_np(proc_t p, struct guarded_kqueue_np_args *uap, int32_t *retval) | |
399 | { | |
400 | if (((uap->guardflags & GUARD_REQUIRED) != GUARD_REQUIRED) || | |
0a7de745 A |
401 | ((uap->guardflags & ~GUARD_ALL) != 0)) { |
402 | return EINVAL; | |
403 | } | |
39236c6e A |
404 | |
405 | int error; | |
406 | struct gfp_crarg crarg = { | |
407 | .gca_attrs = uap->guardflags | |
408 | }; | |
409 | ||
410 | if ((error = copyin(uap->guard, | |
0a7de745 A |
411 | &(crarg.gca_guard), sizeof(crarg.gca_guard))) != 0) { |
412 | return error; | |
413 | } | |
39236c6e | 414 | |
0a7de745 A |
415 | if (crarg.gca_guard == 0) { |
416 | return EINVAL; | |
417 | } | |
39236c6e | 418 | |
cb323159 | 419 | return kqueue_internal(p, guarded_fileproc_alloc_init, &crarg, retval); |
39236c6e A |
420 | } |
421 | ||
422 | /* | |
423 | * int guarded_close_np(int fd, const guardid_t *guard); | |
424 | */ | |
425 | int | |
426 | guarded_close_np(proc_t p, struct guarded_close_np_args *uap, | |
427 | __unused int32_t *retval) | |
428 | { | |
429 | struct guarded_fileproc *gfp; | |
430 | int fd = uap->fd; | |
431 | int error; | |
432 | guardid_t uguard; | |
433 | ||
434 | AUDIT_SYSCLOSE(p, fd); | |
435 | ||
0a7de745 A |
436 | if ((error = copyin(uap->guard, &uguard, sizeof(uguard))) != 0) { |
437 | return error; | |
438 | } | |
39236c6e A |
439 | |
440 | proc_fdlock(p); | |
3e170ce0 | 441 | if ((error = fp_lookup_guarded(p, fd, uguard, &gfp, 1)) != 0) { |
39236c6e | 442 | proc_fdunlock(p); |
0a7de745 | 443 | return error; |
39236c6e | 444 | } |
f427ee49 A |
445 | fp_drop(p, fd, GFP_TO_FP(gfp), 1); |
446 | return fp_close_and_unlock(p, fd, GFP_TO_FP(gfp), 0); | |
39236c6e A |
447 | } |
448 | ||
449 | /* | |
450 | * int | |
451 | * change_fdguard_np(int fd, const guardid_t *guard, u_int guardflags, | |
452 | * const guardid_t *nguard, u_int nguardflags, int *fdflagsp); | |
453 | * | |
454 | * Given a file descriptor, atomically exchange <guard, guardflags> for | |
455 | * a new guard <nguard, nguardflags>, returning the previous fd | |
456 | * flags (see fcntl:F_SETFD) in *fdflagsp. | |
457 | * | |
458 | * This syscall can be used to either (a) add a new guard to an existing | |
459 | * unguarded file descriptor (b) remove the old guard from an existing | |
460 | * guarded file descriptor or (c) change the guard (guardid and/or | |
461 | * guardflags) on a guarded file descriptor. | |
462 | * | |
463 | * If 'guard' is NULL, fd must be unguarded at entry. If the call completes | |
464 | * successfully the fd will be guarded with <nguard, nguardflags>. | |
465 | * | |
466 | * Guarding a file descriptor has some side-effects on the "fdflags" | |
467 | * associated with the descriptor - in particular FD_CLOEXEC is | |
468 | * forced ON unconditionally, and FD_CLOFORK is forced ON by GUARD_CLOSE. | |
469 | * Callers who wish to subsequently restore the state of the fd should save | |
470 | * the value of *fdflagsp after a successful invocation. | |
471 | * | |
472 | * If 'nguard' is NULL, fd must be guarded at entry, <guard, guardflags> | |
473 | * must match with what's already guarding the descriptor, and the | |
474 | * result will be to completely remove the guard. Note also that the | |
0a7de745 | 475 | * fdflags are copied to the descriptor from the incoming *fdflagsp argument. |
39236c6e A |
476 | * |
477 | * If the descriptor is guarded, and neither 'guard' nor 'nguard' is NULL | |
478 | * and <guard, guardflags> matches what's already guarding the descriptor, | |
479 | * then <nguard, nguardflags> becomes the new guard. In this case, even if | |
480 | * the GUARD_CLOSE flag is being cleared, it is still possible to continue | |
481 | * to keep FD_CLOFORK on the descriptor by passing FD_CLOFORK via fdflagsp. | |
482 | * | |
3e170ce0 A |
483 | * (File descriptors whose underlying fileglobs are marked FG_CONFINED are |
484 | * still close-on-fork, regardless of the setting of FD_CLOFORK.) | |
485 | * | |
39236c6e A |
486 | * Example 1: Guard an unguarded descriptor during a set of operations, |
487 | * then restore the original state of the descriptor. | |
488 | * | |
489 | * int sav_flags = 0; | |
490 | * change_fdguard_np(fd, NULL, 0, &myguard, GUARD_CLOSE, &sav_flags); | |
491 | * // do things with now guarded 'fd' | |
492 | * change_fdguard_np(fd, &myguard, GUARD_CLOSE, NULL, 0, &sav_flags); | |
493 | * // fd now unguarded. | |
494 | * | |
495 | * Example 2: Change the guard of a guarded descriptor during a set of | |
496 | * operations, then restore the original state of the descriptor. | |
497 | * | |
498 | * int sav_flags = (gdflags & GUARD_CLOSE) ? FD_CLOFORK : 0; | |
499 | * change_fdguard_np(fd, &gd, gdflags, &myguard, GUARD_CLOSE, &sav_flags); | |
500 | * // do things with 'fd' with a different guard | |
501 | * change_fdguard_np(fd, &myg, GUARD_CLOSE, &gd, gdflags, &sav_flags); | |
502 | * // back to original guarded state | |
3e170ce0 A |
503 | * |
504 | * XXX This SPI is too much of a chainsaw and should be revised. | |
39236c6e A |
505 | */ |
506 | ||
39236c6e A |
507 | int |
508 | change_fdguard_np(proc_t p, struct change_fdguard_np_args *uap, | |
509 | __unused int32_t *retval) | |
510 | { | |
511 | struct fileproc *fp; | |
512 | int fd = uap->fd; | |
513 | int error; | |
514 | guardid_t oldg = 0, newg = 0; | |
515 | int nfdflags = 0; | |
516 | ||
517 | if (0 != uap->guard && | |
0a7de745 A |
518 | 0 != (error = copyin(uap->guard, &oldg, sizeof(oldg)))) { |
519 | return error; /* can't copyin current guard */ | |
520 | } | |
39236c6e | 521 | if (0 != uap->nguard && |
0a7de745 A |
522 | 0 != (error = copyin(uap->nguard, &newg, sizeof(newg)))) { |
523 | return error; /* can't copyin new guard */ | |
524 | } | |
39236c6e | 525 | if (0 != uap->fdflagsp && |
0a7de745 A |
526 | 0 != (error = copyin(uap->fdflagsp, &nfdflags, sizeof(nfdflags)))) { |
527 | return error; /* can't copyin new fdflags */ | |
528 | } | |
39236c6e A |
529 | proc_fdlock(p); |
530 | restart: | |
531 | if ((error = fp_lookup(p, fd, &fp, 1)) != 0) { | |
532 | proc_fdunlock(p); | |
0a7de745 | 533 | return error; |
39236c6e A |
534 | } |
535 | ||
536 | if (0 != uap->fdflagsp) { | |
537 | int ofdflags = FDFLAGS_GET(p, fd); | |
538 | int ofl = ((ofdflags & UF_EXCLOSE) ? FD_CLOEXEC : 0) | | |
0a7de745 | 539 | ((ofdflags & UF_FORKCLOSE) ? FD_CLOFORK : 0); |
39236c6e | 540 | proc_fdunlock(p); |
0a7de745 | 541 | if (0 != (error = copyout(&ofl, uap->fdflagsp, sizeof(ofl)))) { |
39236c6e A |
542 | proc_fdlock(p); |
543 | goto dropout; /* can't copyout old fdflags */ | |
544 | } | |
545 | proc_fdlock(p); | |
546 | } | |
547 | ||
548 | if (FILEPROC_TYPE(fp) == FTYPE_GUARDED) { | |
0a7de745 | 549 | if (0 == uap->guard || 0 == uap->guardflags) { |
39236c6e | 550 | error = EINVAL; /* missing guard! */ |
0a7de745 | 551 | } else if (0 == oldg) { |
39236c6e | 552 | error = EPERM; /* guardids cannot be zero */ |
0a7de745 | 553 | } |
39236c6e | 554 | } else { |
0a7de745 | 555 | if (0 != uap->guard || 0 != uap->guardflags) { |
39236c6e | 556 | error = EINVAL; /* guard provided, but none needed! */ |
0a7de745 | 557 | } |
39236c6e A |
558 | } |
559 | ||
0a7de745 | 560 | if (0 != error) { |
39236c6e | 561 | goto dropout; |
0a7de745 | 562 | } |
39236c6e A |
563 | |
564 | if (0 != uap->nguard) { | |
565 | /* | |
566 | * There's a new guard in town. | |
567 | */ | |
0a7de745 | 568 | if (0 == newg) { |
39236c6e | 569 | error = EINVAL; /* guards cannot contain zero */ |
0a7de745 A |
570 | } else if (((uap->nguardflags & GUARD_REQUIRED) != GUARD_REQUIRED) || |
571 | ((uap->nguardflags & ~GUARD_ALL) != 0)) { | |
39236c6e | 572 | error = EINVAL; /* must have valid attributes too */ |
0a7de745 A |
573 | } |
574 | if (0 != error) { | |
39236c6e | 575 | goto dropout; |
0a7de745 | 576 | } |
39236c6e A |
577 | |
578 | if (FILEPROC_TYPE(fp) == FTYPE_GUARDED) { | |
579 | /* | |
580 | * Replace old guard with new guard | |
581 | */ | |
582 | struct guarded_fileproc *gfp = FP_TO_GFP(fp); | |
583 | ||
0a7de745 | 584 | if (GUARDED_FILEPROC_MAGIC != gfp->gf_magic) { |
39236c6e | 585 | panic("%s: corrupt gfp %p flags %x", |
f427ee49 | 586 | __func__, gfp, fp->fp_flags); |
0a7de745 | 587 | } |
39236c6e A |
588 | |
589 | if (oldg == gfp->gf_guard && | |
590 | uap->guardflags == gfp->gf_attrs) { | |
591 | /* | |
592 | * Must match existing guard + attributes | |
593 | * before we'll swap them to new ones, managing | |
594 | * fdflags "side-effects" as we go. Note that | |
595 | * userland can request FD_CLOFORK semantics. | |
596 | */ | |
0a7de745 | 597 | if (gfp->gf_attrs & GUARD_CLOSE) { |
39236c6e | 598 | FDFLAGS_CLR(p, fd, UF_FORKCLOSE); |
0a7de745 | 599 | } |
39236c6e A |
600 | gfp->gf_guard = newg; |
601 | gfp->gf_attrs = uap->nguardflags; | |
0a7de745 | 602 | if (gfp->gf_attrs & GUARD_CLOSE) { |
39236c6e | 603 | FDFLAGS_SET(p, fd, UF_FORKCLOSE); |
0a7de745 | 604 | } |
39236c6e A |
605 | FDFLAGS_SET(p, fd, |
606 | (nfdflags & FD_CLOFORK) ? UF_FORKCLOSE : 0); | |
3e170ce0 | 607 | /* FG_CONFINED enforced regardless */ |
39236c6e A |
608 | } else { |
609 | error = EPERM; | |
610 | } | |
611 | goto dropout; | |
612 | } else { | |
613 | /* | |
614 | * Add a guard to a previously unguarded descriptor | |
615 | */ | |
f427ee49 | 616 | switch (FILEGLOB_DTYPE(fp->fp_glob)) { |
39236c6e A |
617 | case DTYPE_VNODE: |
618 | case DTYPE_PIPE: | |
619 | case DTYPE_SOCKET: | |
620 | case DTYPE_KQUEUE: | |
5ba3f43e | 621 | case DTYPE_NETPOLICY: |
39236c6e A |
622 | break; |
623 | default: | |
624 | error = ENOTSUP; | |
625 | goto dropout; | |
626 | } | |
627 | ||
628 | proc_fdunlock(p); | |
629 | ||
630 | struct gfp_crarg crarg = { | |
631 | .gca_guard = newg, | |
632 | .gca_attrs = uap->nguardflags | |
633 | }; | |
634 | struct fileproc *nfp = | |
0a7de745 | 635 | guarded_fileproc_alloc_init(&crarg); |
fe8ab488 | 636 | struct guarded_fileproc *gfp; |
39236c6e A |
637 | |
638 | proc_fdlock(p); | |
639 | ||
640 | switch (error = fp_tryswap(p, fd, nfp)) { | |
cb323159 A |
641 | case 0: /* success; guarded-ness comes with side-effects */ |
642 | fp = NULL; | |
39236c6e | 643 | gfp = FP_TO_GFP(nfp); |
0a7de745 | 644 | if (gfp->gf_attrs & GUARD_CLOSE) { |
39236c6e | 645 | FDFLAGS_SET(p, fd, UF_FORKCLOSE); |
0a7de745 | 646 | } |
39236c6e A |
647 | FDFLAGS_SET(p, fd, UF_EXCLOSE); |
648 | (void) fp_drop(p, fd, nfp, 1); | |
39236c6e | 649 | break; |
f427ee49 | 650 | case EKEEPLOOKING: /* fp_iocount indicates a collision */ |
39236c6e A |
651 | (void) fp_drop(p, fd, fp, 1); |
652 | fileproc_free(nfp); | |
653 | goto restart; | |
654 | default: | |
655 | (void) fp_drop(p, fd, fp, 1); | |
656 | fileproc_free(nfp); | |
657 | break; | |
658 | } | |
659 | proc_fdunlock(p); | |
0a7de745 | 660 | return error; |
39236c6e A |
661 | } |
662 | } else { | |
663 | /* | |
664 | * No new guard. | |
665 | */ | |
666 | if (FILEPROC_TYPE(fp) == FTYPE_GUARDED) { | |
667 | /* | |
668 | * Remove the guard altogether. | |
669 | */ | |
670 | struct guarded_fileproc *gfp = FP_TO_GFP(fp); | |
671 | ||
672 | if (0 != uap->nguardflags) { | |
673 | error = EINVAL; | |
674 | goto dropout; | |
675 | } | |
676 | ||
0a7de745 | 677 | if (GUARDED_FILEPROC_MAGIC != gfp->gf_magic) { |
39236c6e | 678 | panic("%s: corrupt gfp %p flags %x", |
f427ee49 | 679 | __func__, gfp, fp->fp_flags); |
0a7de745 | 680 | } |
39236c6e A |
681 | |
682 | if (oldg != gfp->gf_guard || | |
683 | uap->guardflags != gfp->gf_attrs) { | |
684 | error = EPERM; | |
685 | goto dropout; | |
686 | } | |
687 | ||
688 | proc_fdunlock(p); | |
689 | struct fileproc *nfp = fileproc_alloc_init(NULL); | |
690 | proc_fdlock(p); | |
691 | ||
692 | switch (error = fp_tryswap(p, fd, nfp)) { | |
cb323159 A |
693 | case 0: /* success; undo side-effects of guarded-ness */ |
694 | fp = NULL; | |
39236c6e A |
695 | FDFLAGS_CLR(p, fd, UF_FORKCLOSE | UF_EXCLOSE); |
696 | FDFLAGS_SET(p, fd, | |
697 | (nfdflags & FD_CLOFORK) ? UF_FORKCLOSE : 0); | |
3e170ce0 | 698 | /* FG_CONFINED enforced regardless */ |
39236c6e A |
699 | FDFLAGS_SET(p, fd, |
700 | (nfdflags & FD_CLOEXEC) ? UF_EXCLOSE : 0); | |
701 | (void) fp_drop(p, fd, nfp, 1); | |
39236c6e | 702 | break; |
f427ee49 | 703 | case EKEEPLOOKING: /* fp_iocount indicates collision */ |
39236c6e A |
704 | (void) fp_drop(p, fd, fp, 1); |
705 | fileproc_free(nfp); | |
706 | goto restart; | |
707 | default: | |
708 | (void) fp_drop(p, fd, fp, 1); | |
709 | fileproc_free(nfp); | |
710 | break; | |
711 | } | |
712 | proc_fdunlock(p); | |
0a7de745 | 713 | return error; |
39236c6e A |
714 | } else { |
715 | /* | |
716 | * Not already guarded, and no new guard? | |
717 | */ | |
718 | error = EINVAL; | |
719 | } | |
720 | } | |
721 | ||
722 | dropout: | |
723 | (void) fp_drop(p, fd, fp, 1); | |
724 | proc_fdunlock(p); | |
0a7de745 | 725 | return error; |
39236c6e | 726 | } |
fe8ab488 A |
727 | |
728 | /* | |
729 | * user_ssize_t guarded_write_np(int fd, const guardid_t *guard, | |
730 | * user_addr_t cbuf, user_ssize_t nbyte); | |
731 | * | |
732 | * Initial implementation of guarded writes. | |
733 | */ | |
734 | int | |
735 | guarded_write_np(struct proc *p, struct guarded_write_np_args *uap, user_ssize_t *retval) | |
736 | { | |
0a7de745 | 737 | int error; |
fe8ab488 A |
738 | int fd = uap->fd; |
739 | guardid_t uguard; | |
740 | struct fileproc *fp; | |
741 | struct guarded_fileproc *gfp; | |
fe8ab488 A |
742 | |
743 | AUDIT_ARG(fd, fd); | |
744 | ||
0a7de745 A |
745 | if ((error = copyin(uap->guard, &uguard, sizeof(uguard))) != 0) { |
746 | return error; | |
747 | } | |
fe8ab488 | 748 | |
3e170ce0 | 749 | error = fp_lookup_guarded(p, fd, uguard, &gfp, 0); |
0a7de745 A |
750 | if (error) { |
751 | return error; | |
752 | } | |
fe8ab488 A |
753 | |
754 | fp = GFP_TO_FP(gfp); | |
755 | if ((fp->f_flag & FWRITE) == 0) { | |
756 | error = EBADF; | |
757 | } else { | |
fe8ab488 | 758 | struct vfs_context context = *(vfs_context_current()); |
f427ee49 | 759 | context.vc_ucred = fp->fp_glob->fg_cred; |
fe8ab488 A |
760 | |
761 | error = dofilewrite(&context, fp, uap->cbuf, uap->nbyte, | |
0a7de745 | 762 | (off_t)-1, 0, retval); |
0a7de745 | 763 | } |
f427ee49 A |
764 | |
765 | fp_drop(p, fd, fp, 0); | |
766 | ||
0a7de745 | 767 | return error; |
fe8ab488 A |
768 | } |
769 | ||
770 | /* | |
771 | * user_ssize_t guarded_pwrite_np(int fd, const guardid_t *guard, | |
772 | * user_addr_t buf, user_size_t nbyte, off_t offset); | |
773 | * | |
774 | * Initial implementation of guarded pwrites. | |
775 | */ | |
0a7de745 A |
776 | int |
777 | guarded_pwrite_np(struct proc *p, struct guarded_pwrite_np_args *uap, user_ssize_t *retval) | |
778 | { | |
fe8ab488 | 779 | struct fileproc *fp; |
0a7de745 | 780 | int error; |
fe8ab488 A |
781 | int fd = uap->fd; |
782 | vnode_t vp = (vnode_t)0; | |
783 | guardid_t uguard; | |
784 | struct guarded_fileproc *gfp; | |
fe8ab488 A |
785 | |
786 | AUDIT_ARG(fd, fd); | |
787 | ||
0a7de745 A |
788 | if ((error = copyin(uap->guard, &uguard, sizeof(uguard))) != 0) { |
789 | return error; | |
790 | } | |
fe8ab488 | 791 | |
3e170ce0 | 792 | error = fp_lookup_guarded(p, fd, uguard, &gfp, 0); |
0a7de745 A |
793 | if (error) { |
794 | return error; | |
795 | } | |
fe8ab488 A |
796 | |
797 | fp = GFP_TO_FP(gfp); | |
798 | if ((fp->f_flag & FWRITE) == 0) { | |
799 | error = EBADF; | |
800 | } else { | |
801 | struct vfs_context context = *vfs_context_current(); | |
f427ee49 | 802 | context.vc_ucred = fp->fp_glob->fg_cred; |
fe8ab488 | 803 | |
f427ee49 | 804 | if (FILEGLOB_DTYPE(fp->fp_glob) != DTYPE_VNODE) { |
fe8ab488 A |
805 | error = ESPIPE; |
806 | goto errout; | |
807 | } | |
f427ee49 | 808 | vp = (vnode_t)fp->fp_glob->fg_data; |
fe8ab488 A |
809 | if (vnode_isfifo(vp)) { |
810 | error = ESPIPE; | |
811 | goto errout; | |
0a7de745 | 812 | } |
fe8ab488 A |
813 | if ((vp->v_flag & VISTTY)) { |
814 | error = ENXIO; | |
815 | goto errout; | |
816 | } | |
817 | if (uap->offset == (off_t)-1) { | |
818 | error = EINVAL; | |
819 | goto errout; | |
820 | } | |
821 | ||
822 | error = dofilewrite(&context, fp, uap->buf, uap->nbyte, | |
0a7de745 | 823 | uap->offset, FOF_OFFSET, retval); |
fe8ab488 A |
824 | } |
825 | errout: | |
f427ee49 | 826 | fp_drop(p, fd, fp, 0); |
fe8ab488 A |
827 | |
828 | KERNEL_DEBUG_CONSTANT((BSDDBG_CODE(DBG_BSD_SC_EXTENDED_INFO, SYS_guarded_pwrite_np) | DBG_FUNC_NONE), | |
0a7de745 A |
829 | uap->fd, uap->nbyte, (unsigned int)((uap->offset >> 32)), (unsigned int)(uap->offset), 0); |
830 | ||
831 | return error; | |
fe8ab488 A |
832 | } |
833 | ||
834 | /* | |
835 | * user_ssize_t guarded_writev_np(int fd, const guardid_t *guard, | |
836 | * struct iovec *iovp, u_int iovcnt); | |
837 | * | |
838 | * Initial implementation of guarded writev. | |
839 | * | |
840 | */ | |
841 | int | |
842 | guarded_writev_np(struct proc *p, struct guarded_writev_np_args *uap, user_ssize_t *retval) | |
843 | { | |
844 | uio_t auio = NULL; | |
845 | int error; | |
846 | struct fileproc *fp; | |
847 | struct user_iovec *iovp; | |
848 | guardid_t uguard; | |
849 | struct guarded_fileproc *gfp; | |
fe8ab488 A |
850 | |
851 | AUDIT_ARG(fd, uap->fd); | |
852 | ||
853 | /* Verify range bedfore calling uio_create() */ | |
0a7de745 A |
854 | if (uap->iovcnt <= 0 || uap->iovcnt > UIO_MAXIOV) { |
855 | return EINVAL; | |
856 | } | |
fe8ab488 A |
857 | |
858 | /* allocate a uio large enough to hold the number of iovecs passed */ | |
859 | auio = uio_create(uap->iovcnt, 0, | |
0a7de745 A |
860 | (IS_64BIT_PROCESS(p) ? UIO_USERSPACE64 : UIO_USERSPACE32), |
861 | UIO_WRITE); | |
862 | ||
fe8ab488 A |
863 | /* get location of iovecs within the uio. then copyin the iovecs from |
864 | * user space. | |
865 | */ | |
866 | iovp = uio_iovsaddr(auio); | |
867 | if (iovp == NULL) { | |
868 | error = ENOMEM; | |
869 | goto ExitThisRoutine; | |
870 | } | |
871 | error = copyin_user_iovec_array(uap->iovp, | |
0a7de745 A |
872 | IS_64BIT_PROCESS(p) ? UIO_USERSPACE64 : UIO_USERSPACE32, |
873 | uap->iovcnt, iovp); | |
fe8ab488 A |
874 | if (error) { |
875 | goto ExitThisRoutine; | |
876 | } | |
0a7de745 A |
877 | |
878 | /* finalize uio_t for use and do the IO | |
fe8ab488 | 879 | */ |
3e170ce0 A |
880 | error = uio_calculateresid(auio); |
881 | if (error) { | |
882 | goto ExitThisRoutine; | |
883 | } | |
fe8ab488 | 884 | |
0a7de745 | 885 | if ((error = copyin(uap->guard, &uguard, sizeof(uguard))) != 0) { |
fe8ab488 | 886 | goto ExitThisRoutine; |
0a7de745 | 887 | } |
fe8ab488 | 888 | |
3e170ce0 | 889 | error = fp_lookup_guarded(p, uap->fd, uguard, &gfp, 0); |
0a7de745 | 890 | if (error) { |
fe8ab488 | 891 | goto ExitThisRoutine; |
0a7de745 | 892 | } |
fe8ab488 A |
893 | |
894 | fp = GFP_TO_FP(gfp); | |
895 | if ((fp->f_flag & FWRITE) == 0) { | |
896 | error = EBADF; | |
897 | } else { | |
f427ee49 | 898 | error = do_uiowrite(p, fp, auio, 0, retval); |
fe8ab488 | 899 | } |
0a7de745 | 900 | |
f427ee49 | 901 | fp_drop(p, uap->fd, fp, 0); |
fe8ab488 A |
902 | ExitThisRoutine: |
903 | if (auio != NULL) { | |
904 | uio_free(auio); | |
905 | } | |
0a7de745 | 906 | return error; |
fe8ab488 | 907 | } |
39037602 A |
908 | |
909 | /* | |
910 | * int falloc_guarded(struct proc *p, struct fileproc **fp, int *fd, | |
911 | * vfs_context_t ctx, const guardid_t *guard, u_int attrs); | |
912 | * | |
913 | * This SPI is the guarded variant of falloc(). It borrows the same | |
914 | * restrictions as those used by the rest of the guarded_* routines. | |
915 | */ | |
916 | int | |
917 | falloc_guarded(struct proc *p, struct fileproc **fp, int *fd, | |
918 | vfs_context_t ctx, const guardid_t *guard, u_int attrs) | |
919 | { | |
920 | struct gfp_crarg crarg; | |
921 | ||
922 | if (((attrs & GUARD_REQUIRED) != GUARD_REQUIRED) || | |
0a7de745 A |
923 | ((attrs & ~GUARD_ALL) != 0) || (*guard == 0)) { |
924 | return EINVAL; | |
925 | } | |
39037602 | 926 | |
0a7de745 | 927 | bzero(&crarg, sizeof(crarg)); |
39037602 A |
928 | crarg.gca_guard = *guard; |
929 | crarg.gca_attrs = attrs; | |
930 | ||
0a7de745 A |
931 | return falloc_withalloc(p, fp, fd, ctx, guarded_fileproc_alloc_init, |
932 | &crarg); | |
39037602 | 933 | } |
5ba3f43e A |
934 | |
935 | #if CONFIG_MACF && CONFIG_VNGUARD | |
936 | ||
937 | /* | |
938 | * Guarded vnodes | |
939 | * | |
940 | * Uses MAC hooks to guard operations on vnodes in the system. Given an fd, | |
941 | * add data to the label on the fileglob and the vnode it points at. | |
942 | * The data contains a pointer to the fileglob, the set of attributes to | |
943 | * guard, a guard value for uniquification, and the pid of the process | |
944 | * who set the guard up in the first place. | |
945 | * | |
946 | * The fd must have been opened read/write, and the underlying | |
947 | * fileglob is FG_CONFINED so that there's no ambiguity about the | |
948 | * owning process. | |
949 | * | |
950 | * When there's a callback for a vnode operation of interest (rename, unlink, | |
951 | * etc.) check to see if the guard permits that operation, and if not | |
952 | * take an action e.g. log a message or generate a crash report. | |
953 | * | |
954 | * The label is removed from the vnode and the fileglob when the fileglob | |
955 | * is closed. | |
956 | * | |
957 | * The initial action to be taken can be specified by a boot arg (vnguard=0x42) | |
958 | * and change via the "kern.vnguard.flags" sysctl. | |
959 | */ | |
960 | ||
961 | struct vng_owner; | |
962 | ||
963 | struct vng_info { /* lives on the vnode label */ | |
964 | guardid_t vgi_guard; | |
965 | unsigned vgi_attrs; | |
966 | TAILQ_HEAD(, vng_owner) vgi_owners; | |
967 | }; | |
968 | ||
969 | struct vng_owner { /* lives on the fileglob label */ | |
970 | proc_t vgo_p; | |
971 | struct fileglob *vgo_fg; | |
972 | struct vng_info *vgo_vgi; | |
973 | TAILQ_ENTRY(vng_owner) vgo_link; | |
974 | }; | |
975 | ||
976 | static struct vng_info * | |
977 | new_vgi(unsigned attrs, guardid_t guard) | |
978 | { | |
0a7de745 | 979 | struct vng_info *vgi = kalloc(sizeof(*vgi)); |
5ba3f43e A |
980 | vgi->vgi_guard = guard; |
981 | vgi->vgi_attrs = attrs; | |
982 | TAILQ_INIT(&vgi->vgi_owners); | |
983 | return vgi; | |
984 | } | |
985 | ||
986 | static struct vng_owner * | |
987 | new_vgo(proc_t p, struct fileglob *fg) | |
988 | { | |
0a7de745 A |
989 | struct vng_owner *vgo = kalloc(sizeof(*vgo)); |
990 | memset(vgo, 0, sizeof(*vgo)); | |
5ba3f43e A |
991 | vgo->vgo_p = p; |
992 | vgo->vgo_fg = fg; | |
993 | return vgo; | |
994 | } | |
995 | ||
996 | static void | |
997 | vgi_add_vgo(struct vng_info *vgi, struct vng_owner *vgo) | |
998 | { | |
999 | vgo->vgo_vgi = vgi; | |
1000 | TAILQ_INSERT_HEAD(&vgi->vgi_owners, vgo, vgo_link); | |
1001 | } | |
1002 | ||
1003 | static boolean_t | |
1004 | vgi_remove_vgo(struct vng_info *vgi, struct vng_owner *vgo) | |
1005 | { | |
1006 | TAILQ_REMOVE(&vgi->vgi_owners, vgo, vgo_link); | |
1007 | vgo->vgo_vgi = NULL; | |
1008 | return TAILQ_EMPTY(&vgi->vgi_owners); | |
1009 | } | |
1010 | ||
1011 | static void | |
1012 | free_vgi(struct vng_info *vgi) | |
1013 | { | |
1014 | assert(TAILQ_EMPTY(&vgi->vgi_owners)); | |
1015 | #if DEVELOP || DEBUG | |
0a7de745 | 1016 | memset(vgi, 0xbeadfade, sizeof(*vgi)); |
5ba3f43e | 1017 | #endif |
0a7de745 | 1018 | kfree(vgi, sizeof(*vgi)); |
5ba3f43e A |
1019 | } |
1020 | ||
1021 | static void | |
1022 | free_vgo(struct vng_owner *vgo) | |
1023 | { | |
1024 | #if DEVELOP || DEBUG | |
0a7de745 | 1025 | memset(vgo, 0x2bedf1d0, sizeof(*vgo)); |
5ba3f43e | 1026 | #endif |
0a7de745 | 1027 | kfree(vgo, sizeof(*vgo)); |
5ba3f43e A |
1028 | } |
1029 | ||
1030 | static int label_slot; | |
1031 | static lck_rw_t llock; | |
1032 | static lck_grp_t *llock_grp; | |
1033 | ||
1034 | static __inline void * | |
1035 | vng_lbl_get(struct label *label) | |
1036 | { | |
1037 | lck_rw_assert(&llock, LCK_RW_ASSERT_HELD); | |
1038 | void *data; | |
0a7de745 | 1039 | if (NULL == label) { |
5ba3f43e | 1040 | data = NULL; |
0a7de745 | 1041 | } else { |
5ba3f43e | 1042 | data = (void *)mac_label_get(label, label_slot); |
0a7de745 | 1043 | } |
5ba3f43e A |
1044 | return data; |
1045 | } | |
1046 | ||
1047 | static __inline struct vng_info * | |
1048 | vng_lbl_get_withattr(struct label *label, unsigned attrmask) | |
1049 | { | |
1050 | struct vng_info *vgi = vng_lbl_get(label); | |
1051 | assert(NULL == vgi || (vgi->vgi_attrs & ~VNG_ALL) == 0); | |
0a7de745 | 1052 | if (NULL != vgi && 0 == (vgi->vgi_attrs & attrmask)) { |
5ba3f43e | 1053 | vgi = NULL; |
0a7de745 | 1054 | } |
5ba3f43e A |
1055 | return vgi; |
1056 | } | |
1057 | ||
1058 | static __inline void | |
1059 | vng_lbl_set(struct label *label, void *data) | |
1060 | { | |
1061 | assert(NULL != label); | |
1062 | lck_rw_assert(&llock, LCK_RW_ASSERT_EXCLUSIVE); | |
1063 | mac_label_set(label, label_slot, (intptr_t)data); | |
1064 | } | |
1065 | ||
cb323159 A |
1066 | static int |
1067 | vnguard_sysc_getguardattr(proc_t p, struct vnguard_getattr *vga) | |
1068 | { | |
1069 | const int fd = vga->vga_fd; | |
1070 | ||
1071 | if (0 == vga->vga_guard) { | |
1072 | return EINVAL; | |
1073 | } | |
1074 | ||
1075 | int error; | |
1076 | struct fileproc *fp; | |
1077 | if (0 != (error = fp_lookup(p, fd, &fp, 0))) { | |
1078 | return error; | |
1079 | } | |
1080 | do { | |
f427ee49 | 1081 | struct fileglob *fg = fp->fp_glob; |
cb323159 A |
1082 | if (FILEGLOB_DTYPE(fg) != DTYPE_VNODE) { |
1083 | error = EBADF; | |
1084 | break; | |
1085 | } | |
1086 | struct vnode *vp = fg->fg_data; | |
1087 | if (!vnode_isreg(vp) || NULL == vp->v_mount) { | |
1088 | error = EBADF; | |
1089 | break; | |
1090 | } | |
1091 | error = vnode_getwithref(vp); | |
1092 | if (0 != error) { | |
1093 | break; | |
1094 | } | |
1095 | ||
1096 | vga->vga_attrs = 0; | |
1097 | ||
1098 | lck_rw_lock_shared(&llock); | |
1099 | ||
1100 | if (NULL != vp->v_label) { | |
1101 | const struct vng_info *vgi = vng_lbl_get(vp->v_label); | |
1102 | if (NULL != vgi) { | |
1103 | if (vgi->vgi_guard != vga->vga_guard) { | |
1104 | error = EPERM; | |
1105 | } else { | |
1106 | vga->vga_attrs = vgi->vgi_attrs; | |
1107 | } | |
1108 | } | |
1109 | } | |
1110 | ||
1111 | lck_rw_unlock_shared(&llock); | |
1112 | vnode_put(vp); | |
1113 | } while (0); | |
1114 | ||
1115 | fp_drop(p, fd, fp, 0); | |
1116 | return error; | |
1117 | } | |
1118 | ||
5ba3f43e A |
1119 | static int |
1120 | vnguard_sysc_setguard(proc_t p, const struct vnguard_set *vns) | |
1121 | { | |
1122 | const int fd = vns->vns_fd; | |
1123 | ||
1124 | if ((vns->vns_attrs & ~VNG_ALL) != 0 || | |
0a7de745 | 1125 | 0 == vns->vns_attrs || 0 == vns->vns_guard) { |
5ba3f43e | 1126 | return EINVAL; |
0a7de745 | 1127 | } |
5ba3f43e A |
1128 | |
1129 | int error; | |
1130 | struct fileproc *fp; | |
0a7de745 | 1131 | if (0 != (error = fp_lookup(p, fd, &fp, 0))) { |
5ba3f43e | 1132 | return error; |
0a7de745 | 1133 | } |
5ba3f43e A |
1134 | do { |
1135 | /* | |
1136 | * To avoid trivial DoS, insist that the caller | |
1137 | * has read/write access to the file. | |
1138 | */ | |
0a7de745 | 1139 | if ((FREAD | FWRITE) != (fp->f_flag & (FREAD | FWRITE))) { |
5ba3f43e A |
1140 | error = EBADF; |
1141 | break; | |
1142 | } | |
f427ee49 | 1143 | struct fileglob *fg = fp->fp_glob; |
5ba3f43e A |
1144 | if (FILEGLOB_DTYPE(fg) != DTYPE_VNODE) { |
1145 | error = EBADF; | |
1146 | break; | |
1147 | } | |
1148 | /* | |
1149 | * Confinement means there's only one fd pointing at | |
1150 | * this fileglob, and will always be associated with | |
1151 | * this pid. | |
1152 | */ | |
1153 | if (0 == (FG_CONFINED & fg->fg_lflags)) { | |
1154 | error = EBADF; | |
1155 | break; | |
1156 | } | |
1157 | struct vnode *vp = fg->fg_data; | |
1158 | if (!vnode_isreg(vp) || NULL == vp->v_mount) { | |
1159 | error = EBADF; | |
1160 | break; | |
1161 | } | |
1162 | error = vnode_getwithref(vp); | |
1163 | if (0 != error) { | |
5ba3f43e A |
1164 | break; |
1165 | } | |
cb323159 | 1166 | |
5ba3f43e A |
1167 | /* Ensure the target vnode -has- a label */ |
1168 | struct vfs_context *ctx = vfs_context_current(); | |
1169 | mac_vnode_label_update(ctx, vp, NULL); | |
1170 | ||
1171 | struct vng_info *nvgi = new_vgi(vns->vns_attrs, vns->vns_guard); | |
1172 | struct vng_owner *nvgo = new_vgo(p, fg); | |
1173 | ||
1174 | lck_rw_lock_exclusive(&llock); | |
1175 | ||
1176 | do { | |
1177 | /* | |
1178 | * A vnode guard is associated with one or more | |
1179 | * fileglobs in one or more processes. | |
1180 | */ | |
1181 | struct vng_info *vgi = vng_lbl_get(vp->v_label); | |
1182 | struct vng_owner *vgo = vng_lbl_get(fg->fg_label); | |
1183 | ||
1184 | if (NULL == vgi) { | |
1185 | /* vnode unguarded, add the first guard */ | |
0a7de745 | 1186 | if (NULL != vgo) { |
5ba3f43e | 1187 | panic("vnguard label on fileglob " |
0a7de745 A |
1188 | "but not vnode"); |
1189 | } | |
5ba3f43e A |
1190 | /* add a kusecount so we can unlabel later */ |
1191 | error = vnode_ref_ext(vp, O_EVTONLY, 0); | |
1192 | if (0 == error) { | |
1193 | /* add the guard */ | |
1194 | vgi_add_vgo(nvgi, nvgo); | |
1195 | vng_lbl_set(vp->v_label, nvgi); | |
1196 | vng_lbl_set(fg->fg_label, nvgo); | |
1197 | } else { | |
1198 | free_vgo(nvgo); | |
1199 | free_vgi(nvgi); | |
1200 | } | |
1201 | } else { | |
1202 | /* vnode already guarded */ | |
1203 | free_vgi(nvgi); | |
0a7de745 | 1204 | if (vgi->vgi_guard != vns->vns_guard) { |
5ba3f43e | 1205 | error = EPERM; /* guard mismatch */ |
0a7de745 | 1206 | } else if (vgi->vgi_attrs != vns->vns_attrs) { |
cb323159 A |
1207 | /* |
1208 | * Temporary workaround for older versions of SQLite: | |
1209 | * allow newer guard attributes to be silently cleared. | |
1210 | */ | |
1211 | const unsigned mask = ~(VNG_WRITE_OTHER | VNG_TRUNC_OTHER); | |
1212 | if ((vgi->vgi_attrs & mask) == (vns->vns_attrs & mask)) { | |
1213 | vgi->vgi_attrs &= vns->vns_attrs; | |
1214 | } else { | |
1215 | error = EACCES; /* attr mismatch */ | |
1216 | } | |
0a7de745 | 1217 | } |
5ba3f43e A |
1218 | if (0 != error || NULL != vgo) { |
1219 | free_vgo(nvgo); | |
1220 | break; | |
1221 | } | |
1222 | /* record shared ownership */ | |
1223 | vgi_add_vgo(vgi, nvgo); | |
1224 | vng_lbl_set(fg->fg_label, nvgo); | |
1225 | } | |
1226 | } while (0); | |
1227 | ||
1228 | lck_rw_unlock_exclusive(&llock); | |
1229 | vnode_put(vp); | |
1230 | } while (0); | |
1231 | ||
1232 | fp_drop(p, fd, fp, 0); | |
1233 | return error; | |
1234 | } | |
1235 | ||
1236 | static int | |
1237 | vng_policy_syscall(proc_t p, int cmd, user_addr_t arg) | |
1238 | { | |
1239 | int error = EINVAL; | |
1240 | ||
1241 | switch (cmd) { | |
1242 | case VNG_SYSC_PING: | |
0a7de745 | 1243 | if (0 == arg) { |
5ba3f43e | 1244 | error = 0; |
0a7de745 | 1245 | } |
5ba3f43e A |
1246 | break; |
1247 | case VNG_SYSC_SET_GUARD: { | |
1248 | struct vnguard_set vns; | |
0a7de745 A |
1249 | error = copyin(arg, (void *)&vns, sizeof(vns)); |
1250 | if (error) { | |
5ba3f43e | 1251 | break; |
0a7de745 | 1252 | } |
5ba3f43e A |
1253 | error = vnguard_sysc_setguard(p, &vns); |
1254 | break; | |
1255 | } | |
cb323159 A |
1256 | case VNG_SYSC_GET_ATTR: { |
1257 | struct vnguard_getattr vga; | |
1258 | error = copyin(arg, (void *)&vga, sizeof(vga)); | |
1259 | if (error) { | |
1260 | break; | |
1261 | } | |
1262 | error = vnguard_sysc_getguardattr(p, &vga); | |
1263 | if (error) { | |
1264 | break; | |
1265 | } | |
1266 | error = copyout((void *)&vga, arg, sizeof(vga)); | |
1267 | break; | |
1268 | } | |
5ba3f43e A |
1269 | default: |
1270 | break; | |
1271 | } | |
0a7de745 | 1272 | return error; |
5ba3f43e A |
1273 | } |
1274 | ||
1275 | /* | |
1276 | * This is called just before the fileglob disappears in fg_free(). | |
1277 | * Take the exclusive lock: no other thread can add or remove | |
1278 | * a vng_info to any vnode in the system. | |
1279 | */ | |
1280 | static void | |
1281 | vng_file_label_destroy(struct label *label) | |
1282 | { | |
1283 | lck_rw_lock_exclusive(&llock); | |
1284 | struct vng_owner *lvgo = vng_lbl_get(label); | |
1285 | if (lvgo) { | |
1286 | vng_lbl_set(label, 0); | |
1287 | struct vng_info *vgi = lvgo->vgo_vgi; | |
1288 | assert(vgi); | |
1289 | if (vgi_remove_vgo(vgi, lvgo)) { | |
1290 | /* that was the last reference */ | |
1291 | vgi->vgi_attrs = 0; | |
1292 | struct fileglob *fg = lvgo->vgo_fg; | |
1293 | assert(fg); | |
1294 | if (DTYPE_VNODE == FILEGLOB_DTYPE(fg)) { | |
1295 | struct vnode *vp = fg->fg_data; | |
1296 | int error = vnode_getwithref(vp); | |
1297 | if (0 == error) { | |
1298 | vng_lbl_set(vp->v_label, 0); | |
1299 | lck_rw_unlock_exclusive(&llock); | |
1300 | /* may trigger VNOP_INACTIVE */ | |
1301 | vnode_rele_ext(vp, O_EVTONLY, 0); | |
1302 | vnode_put(vp); | |
1303 | free_vgi(vgi); | |
1304 | free_vgo(lvgo); | |
1305 | return; | |
1306 | } | |
1307 | } | |
1308 | } | |
1309 | free_vgo(lvgo); | |
1310 | } | |
1311 | lck_rw_unlock_exclusive(&llock); | |
1312 | } | |
1313 | ||
d9a64523 A |
1314 | static os_reason_t |
1315 | vng_reason_from_pathname(const char *path, uint32_t pathlen) | |
1316 | { | |
1317 | os_reason_t r = os_reason_create(OS_REASON_GUARD, GUARD_REASON_VNODE); | |
0a7de745 A |
1318 | if (NULL == r) { |
1319 | return r; | |
1320 | } | |
d9a64523 A |
1321 | /* |
1322 | * If the pathname is very long, just keep the trailing part | |
1323 | */ | |
1324 | const uint32_t pathmax = 3 * EXIT_REASON_USER_DESC_MAX_LEN / 4; | |
1325 | if (pathlen > pathmax) { | |
1326 | path += (pathlen - pathmax); | |
1327 | pathlen = pathmax; | |
1328 | } | |
1329 | uint32_t rsize = kcdata_estimate_required_buffer_size(1, pathlen); | |
1330 | if (0 == os_reason_alloc_buffer(r, rsize)) { | |
1331 | struct kcdata_descriptor *kcd = &r->osr_kcd_descriptor; | |
1332 | mach_vm_address_t addr; | |
1333 | if (kcdata_get_memory_addr(kcd, | |
1334 | EXIT_REASON_USER_DESC, pathlen, &addr) == KERN_SUCCESS) { | |
1335 | kcdata_memcpy(kcd, addr, path, pathlen); | |
0a7de745 | 1336 | return r; |
d9a64523 A |
1337 | } |
1338 | } | |
1339 | os_reason_free(r); | |
0a7de745 | 1340 | return OS_REASON_NULL; |
d9a64523 A |
1341 | } |
1342 | ||
5ba3f43e A |
1343 | static int vng_policy_flags; |
1344 | ||
cb323159 A |
1345 | /* |
1346 | * Note: if an EXC_GUARD is generated, llock will be dropped and | |
1347 | * subsequently reacquired by this routine. Data derived from | |
1348 | * any label in the caller should be regenerated. | |
1349 | */ | |
5ba3f43e A |
1350 | static int |
1351 | vng_guard_violation(const struct vng_info *vgi, | |
d9a64523 | 1352 | unsigned opval, vnode_t vp) |
5ba3f43e A |
1353 | { |
1354 | int retval = 0; | |
1355 | ||
1356 | if (vng_policy_flags & kVNG_POLICY_EPERM) { | |
1357 | /* deny the operation */ | |
1358 | retval = EPERM; | |
1359 | } | |
1360 | ||
0a7de745 | 1361 | if (vng_policy_flags & (kVNG_POLICY_LOGMSG | kVNG_POLICY_UPRINTMSG)) { |
5ba3f43e A |
1362 | /* log a message */ |
1363 | const char *op; | |
1364 | switch (opval) { | |
1365 | case VNG_RENAME_FROM: | |
1366 | op = "rename-from"; | |
1367 | break; | |
1368 | case VNG_RENAME_TO: | |
1369 | op = "rename-to"; | |
1370 | break; | |
1371 | case VNG_UNLINK: | |
1372 | op = "unlink"; | |
1373 | break; | |
1374 | case VNG_LINK: | |
1375 | op = "link"; | |
1376 | break; | |
1377 | case VNG_EXCHDATA: | |
1378 | op = "exchdata"; | |
1379 | break; | |
1380 | case VNG_WRITE_OTHER: | |
1381 | op = "write"; | |
1382 | break; | |
1383 | case VNG_TRUNC_OTHER: | |
1384 | op = "truncate"; | |
1385 | break; | |
1386 | default: | |
1387 | op = "(unknown)"; | |
1388 | break; | |
1389 | } | |
d9a64523 A |
1390 | |
1391 | const char *nm = vnode_getname(vp); | |
5ba3f43e A |
1392 | proc_t p = current_proc(); |
1393 | const struct vng_owner *vgo; | |
1394 | TAILQ_FOREACH(vgo, &vgi->vgi_owners, vgo_link) { | |
d9a64523 A |
1395 | const char fmt[] = |
1396 | "%s[%d]: %s%s: '%s' guarded by %s[%d] (0x%llx)\n"; | |
1397 | ||
1398 | if (vng_policy_flags & kVNG_POLICY_LOGMSG) { | |
1399 | printf(fmt, | |
1400 | proc_name_address(p), proc_pid(p), op, | |
1401 | 0 != retval ? " denied" : "", | |
1402 | NULL != nm ? nm : "(unknown)", | |
1403 | proc_name_address(vgo->vgo_p), | |
1404 | proc_pid(vgo->vgo_p), vgi->vgi_guard); | |
1405 | } | |
1406 | if (vng_policy_flags & kVNG_POLICY_UPRINTMSG) { | |
1407 | uprintf(fmt, | |
1408 | proc_name_address(p), proc_pid(p), op, | |
1409 | 0 != retval ? " denied" : "", | |
1410 | NULL != nm ? nm : "(unknown)", | |
1411 | proc_name_address(vgo->vgo_p), | |
1412 | proc_pid(vgo->vgo_p), vgi->vgi_guard); | |
1413 | } | |
5ba3f43e | 1414 | } |
0a7de745 | 1415 | if (NULL != nm) { |
d9a64523 | 1416 | vnode_putname(nm); |
0a7de745 | 1417 | } |
5ba3f43e A |
1418 | } |
1419 | ||
0a7de745 | 1420 | if (vng_policy_flags & (kVNG_POLICY_EXC | kVNG_POLICY_EXC_CORPSE)) { |
5ba3f43e A |
1421 | /* EXC_GUARD exception */ |
1422 | const struct vng_owner *vgo = TAILQ_FIRST(&vgi->vgi_owners); | |
1423 | pid_t pid = vgo ? proc_pid(vgo->vgo_p) : 0; | |
1424 | mach_exception_code_t code; | |
1425 | mach_exception_subcode_t subcode; | |
1426 | ||
1427 | code = 0; | |
1428 | EXC_GUARD_ENCODE_TYPE(code, GUARD_TYPE_VN); | |
1429 | EXC_GUARD_ENCODE_FLAVOR(code, opval); | |
1430 | EXC_GUARD_ENCODE_TARGET(code, pid); | |
1431 | subcode = vgi->vgi_guard; | |
1432 | ||
cb323159 A |
1433 | lck_rw_unlock_shared(&llock); |
1434 | ||
5ba3f43e | 1435 | if (vng_policy_flags & kVNG_POLICY_EXC_CORPSE) { |
d9a64523 A |
1436 | char *path; |
1437 | int len = MAXPATHLEN; | |
1438 | MALLOC(path, char *, len, M_TEMP, M_WAITOK); | |
1439 | os_reason_t r = NULL; | |
1440 | if (NULL != path) { | |
1441 | vn_getpath(vp, path, &len); | |
0a7de745 | 1442 | if (*path && len) { |
d9a64523 | 1443 | r = vng_reason_from_pathname(path, len); |
0a7de745 | 1444 | } |
d9a64523 A |
1445 | } |
1446 | task_violated_guard(code, subcode, r); /* not fatal */ | |
0a7de745 | 1447 | if (NULL != r) { |
d9a64523 | 1448 | os_reason_free(r); |
0a7de745 A |
1449 | } |
1450 | if (NULL != path) { | |
d9a64523 | 1451 | FREE(path, M_TEMP); |
0a7de745 | 1452 | } |
5ba3f43e A |
1453 | } else { |
1454 | thread_t t = current_thread(); | |
cb323159 | 1455 | thread_guard_violation(t, code, subcode, TRUE); |
5ba3f43e | 1456 | } |
cb323159 A |
1457 | |
1458 | lck_rw_lock_shared(&llock); | |
5ba3f43e A |
1459 | } else if (vng_policy_flags & kVNG_POLICY_SIGKILL) { |
1460 | proc_t p = current_proc(); | |
1461 | psignal(p, SIGKILL); | |
1462 | } | |
1463 | ||
0a7de745 | 1464 | return retval; |
5ba3f43e A |
1465 | } |
1466 | ||
1467 | /* | |
d9a64523 | 1468 | * A fatal vnode guard was tripped on this thread. |
5ba3f43e A |
1469 | * |
1470 | * (Invoked before returning to userland from the syscall handler.) | |
1471 | */ | |
1472 | void | |
1473 | vn_guard_ast(thread_t __unused t, | |
1474 | mach_exception_data_type_t code, mach_exception_data_type_t subcode) | |
1475 | { | |
1476 | task_exception_notify(EXC_GUARD, code, subcode); | |
1477 | proc_t p = current_proc(); | |
1478 | psignal(p, SIGKILL); | |
1479 | } | |
1480 | ||
1481 | /* | |
1482 | * vnode callbacks | |
1483 | */ | |
1484 | ||
1485 | static int | |
1486 | vng_vnode_check_rename(kauth_cred_t __unused cred, | |
1487 | struct vnode *__unused dvp, struct label *__unused dlabel, | |
d9a64523 A |
1488 | struct vnode *vp, struct label *label, |
1489 | struct componentname *__unused cnp, | |
5ba3f43e | 1490 | struct vnode *__unused tdvp, struct label *__unused tdlabel, |
d9a64523 A |
1491 | struct vnode *tvp, struct label *tlabel, |
1492 | struct componentname *__unused tcnp) | |
5ba3f43e A |
1493 | { |
1494 | int error = 0; | |
1495 | if (NULL != label || NULL != tlabel) { | |
1496 | lck_rw_lock_shared(&llock); | |
1497 | const struct vng_info *vgi = | |
1498 | vng_lbl_get_withattr(label, VNG_RENAME_FROM); | |
0a7de745 | 1499 | if (NULL != vgi) { |
d9a64523 | 1500 | error = vng_guard_violation(vgi, VNG_RENAME_FROM, vp); |
0a7de745 | 1501 | } |
5ba3f43e A |
1502 | if (0 == error) { |
1503 | vgi = vng_lbl_get_withattr(tlabel, VNG_RENAME_TO); | |
0a7de745 | 1504 | if (NULL != vgi) { |
5ba3f43e | 1505 | error = vng_guard_violation(vgi, |
d9a64523 | 1506 | VNG_RENAME_TO, tvp); |
0a7de745 | 1507 | } |
5ba3f43e A |
1508 | } |
1509 | lck_rw_unlock_shared(&llock); | |
1510 | } | |
0a7de745 | 1511 | return error; |
5ba3f43e A |
1512 | } |
1513 | ||
1514 | static int | |
1515 | vng_vnode_check_link(kauth_cred_t __unused cred, | |
1516 | struct vnode *__unused dvp, struct label *__unused dlabel, | |
1517 | struct vnode *vp, struct label *label, struct componentname *__unused cnp) | |
1518 | { | |
1519 | int error = 0; | |
1520 | if (NULL != label) { | |
1521 | lck_rw_lock_shared(&llock); | |
1522 | const struct vng_info *vgi = | |
0a7de745 A |
1523 | vng_lbl_get_withattr(label, VNG_LINK); |
1524 | if (vgi) { | |
d9a64523 | 1525 | error = vng_guard_violation(vgi, VNG_LINK, vp); |
0a7de745 | 1526 | } |
5ba3f43e A |
1527 | lck_rw_unlock_shared(&llock); |
1528 | } | |
0a7de745 | 1529 | return error; |
5ba3f43e A |
1530 | } |
1531 | ||
1532 | static int | |
1533 | vng_vnode_check_unlink(kauth_cred_t __unused cred, | |
1534 | struct vnode *__unused dvp, struct label *__unused dlabel, | |
d9a64523 | 1535 | struct vnode *vp, struct label *label, struct componentname *__unused cnp) |
5ba3f43e A |
1536 | { |
1537 | int error = 0; | |
1538 | if (NULL != label) { | |
1539 | lck_rw_lock_shared(&llock); | |
1540 | const struct vng_info *vgi = | |
1541 | vng_lbl_get_withattr(label, VNG_UNLINK); | |
0a7de745 | 1542 | if (vgi) { |
d9a64523 | 1543 | error = vng_guard_violation(vgi, VNG_UNLINK, vp); |
0a7de745 | 1544 | } |
5ba3f43e A |
1545 | lck_rw_unlock_shared(&llock); |
1546 | } | |
0a7de745 | 1547 | return error; |
5ba3f43e A |
1548 | } |
1549 | ||
1550 | /* | |
1551 | * Only check violations for writes performed by "other processes" | |
1552 | */ | |
1553 | static int | |
1554 | vng_vnode_check_write(kauth_cred_t __unused actv_cred, | |
1555 | kauth_cred_t __unused file_cred, struct vnode *vp, struct label *label) | |
1556 | { | |
1557 | int error = 0; | |
1558 | if (NULL != label) { | |
1559 | lck_rw_lock_shared(&llock); | |
1560 | const struct vng_info *vgi = | |
1561 | vng_lbl_get_withattr(label, VNG_WRITE_OTHER); | |
1562 | if (vgi) { | |
1563 | proc_t p = current_proc(); | |
1564 | const struct vng_owner *vgo; | |
1565 | TAILQ_FOREACH(vgo, &vgi->vgi_owners, vgo_link) { | |
0a7de745 | 1566 | if (vgo->vgo_p == p) { |
5ba3f43e | 1567 | goto done; |
0a7de745 | 1568 | } |
5ba3f43e | 1569 | } |
d9a64523 | 1570 | error = vng_guard_violation(vgi, VNG_WRITE_OTHER, vp); |
5ba3f43e | 1571 | } |
0a7de745 | 1572 | done: |
5ba3f43e A |
1573 | lck_rw_unlock_shared(&llock); |
1574 | } | |
0a7de745 | 1575 | return error; |
5ba3f43e A |
1576 | } |
1577 | ||
1578 | /* | |
1579 | * Only check violations for truncates performed by "other processes" | |
1580 | */ | |
1581 | static int | |
1582 | vng_vnode_check_truncate(kauth_cred_t __unused actv_cred, | |
1583 | kauth_cred_t __unused file_cred, struct vnode *vp, | |
1584 | struct label *label) | |
1585 | { | |
1586 | int error = 0; | |
1587 | if (NULL != label) { | |
1588 | lck_rw_lock_shared(&llock); | |
1589 | const struct vng_info *vgi = | |
1590 | vng_lbl_get_withattr(label, VNG_TRUNC_OTHER); | |
1591 | if (vgi) { | |
1592 | proc_t p = current_proc(); | |
1593 | const struct vng_owner *vgo; | |
1594 | TAILQ_FOREACH(vgo, &vgi->vgi_owners, vgo_link) { | |
0a7de745 | 1595 | if (vgo->vgo_p == p) { |
5ba3f43e | 1596 | goto done; |
0a7de745 | 1597 | } |
5ba3f43e | 1598 | } |
d9a64523 | 1599 | error = vng_guard_violation(vgi, VNG_TRUNC_OTHER, vp); |
5ba3f43e | 1600 | } |
0a7de745 | 1601 | done: |
5ba3f43e A |
1602 | lck_rw_unlock_shared(&llock); |
1603 | } | |
1604 | return error; | |
1605 | } | |
1606 | ||
1607 | static int | |
1608 | vng_vnode_check_exchangedata(kauth_cred_t __unused cred, | |
1609 | struct vnode *fvp, struct label *flabel, | |
1610 | struct vnode *svp, struct label *slabel) | |
1611 | { | |
1612 | int error = 0; | |
1613 | if (NULL != flabel || NULL != slabel) { | |
1614 | lck_rw_lock_shared(&llock); | |
1615 | const struct vng_info *vgi = | |
0a7de745 A |
1616 | vng_lbl_get_withattr(flabel, VNG_EXCHDATA); |
1617 | if (NULL != vgi) { | |
d9a64523 | 1618 | error = vng_guard_violation(vgi, VNG_EXCHDATA, fvp); |
0a7de745 | 1619 | } |
5ba3f43e A |
1620 | if (0 == error) { |
1621 | vgi = vng_lbl_get_withattr(slabel, VNG_EXCHDATA); | |
0a7de745 | 1622 | if (NULL != vgi) { |
5ba3f43e | 1623 | error = vng_guard_violation(vgi, |
d9a64523 | 1624 | VNG_EXCHDATA, svp); |
0a7de745 | 1625 | } |
5ba3f43e A |
1626 | } |
1627 | lck_rw_unlock_shared(&llock); | |
1628 | } | |
0a7de745 | 1629 | return error; |
d9a64523 A |
1630 | } |
1631 | ||
1632 | /* Intercept open-time truncations (by "other") of a guarded vnode */ | |
1633 | ||
1634 | static int | |
1635 | vng_vnode_check_open(kauth_cred_t cred, | |
1636 | struct vnode *vp, struct label *label, int acc_mode) | |
1637 | { | |
0a7de745 A |
1638 | if (0 == (acc_mode & O_TRUNC)) { |
1639 | return 0; | |
1640 | } | |
1641 | return vng_vnode_check_truncate(cred, NULL, vp, label); | |
5ba3f43e A |
1642 | } |
1643 | ||
1644 | /* | |
1645 | * Configuration gorp | |
1646 | */ | |
1647 | ||
1648 | static void | |
1649 | vng_init(struct mac_policy_conf *mpc) | |
1650 | { | |
1651 | llock_grp = lck_grp_alloc_init(mpc->mpc_name, LCK_GRP_ATTR_NULL); | |
1652 | lck_rw_init(&llock, llock_grp, LCK_ATTR_NULL); | |
1653 | } | |
1654 | ||
1655 | SECURITY_READ_ONLY_EARLY(static struct mac_policy_ops) vng_policy_ops = { | |
1656 | .mpo_file_label_destroy = vng_file_label_destroy, | |
1657 | ||
1658 | .mpo_vnode_check_link = vng_vnode_check_link, | |
1659 | .mpo_vnode_check_unlink = vng_vnode_check_unlink, | |
1660 | .mpo_vnode_check_rename = vng_vnode_check_rename, | |
1661 | .mpo_vnode_check_write = vng_vnode_check_write, | |
1662 | .mpo_vnode_check_truncate = vng_vnode_check_truncate, | |
1663 | .mpo_vnode_check_exchangedata = vng_vnode_check_exchangedata, | |
d9a64523 | 1664 | .mpo_vnode_check_open = vng_vnode_check_open, |
5ba3f43e A |
1665 | |
1666 | .mpo_policy_syscall = vng_policy_syscall, | |
1667 | .mpo_policy_init = vng_init, | |
1668 | }; | |
1669 | ||
1670 | static const char *vng_labelnames[] = { | |
1671 | "vnguard", | |
1672 | }; | |
1673 | ||
1674 | #define ACOUNT(arr) ((unsigned)(sizeof (arr) / sizeof (arr[0]))) | |
1675 | ||
1676 | SECURITY_READ_ONLY_LATE(static struct mac_policy_conf) vng_policy_conf = { | |
1677 | .mpc_name = VNG_POLICY_NAME, | |
1678 | .mpc_fullname = "Guarded vnode policy", | |
1679 | .mpc_field_off = &label_slot, | |
1680 | .mpc_labelnames = vng_labelnames, | |
1681 | .mpc_labelname_count = ACOUNT(vng_labelnames), | |
1682 | .mpc_ops = &vng_policy_ops, | |
1683 | .mpc_loadtime_flags = 0, | |
1684 | .mpc_runtime_flags = 0 | |
1685 | }; | |
1686 | ||
cb323159 | 1687 | SECURITY_READ_ONLY_LATE(static mac_policy_handle_t) vng_policy_handle; |
5ba3f43e A |
1688 | |
1689 | void | |
1690 | vnguard_policy_init(void) | |
1691 | { | |
0a7de745 | 1692 | if (0 == PE_i_can_has_debugger(NULL)) { |
5ba3f43e | 1693 | return; |
0a7de745 | 1694 | } |
d9a64523 | 1695 | vng_policy_flags = kVNG_POLICY_LOGMSG | |
0a7de745 A |
1696 | kVNG_POLICY_EXC_CORPSE | kVNG_POLICY_UPRINTMSG; |
1697 | PE_parse_boot_argn("vnguard", &vng_policy_flags, sizeof(vng_policy_flags)); | |
1698 | if (vng_policy_flags) { | |
5ba3f43e | 1699 | mac_policy_register(&vng_policy_conf, &vng_policy_handle, NULL); |
0a7de745 | 1700 | } |
5ba3f43e A |
1701 | } |
1702 | ||
1703 | #if DEBUG || DEVELOPMENT | |
1704 | #include <sys/sysctl.h> | |
1705 | ||
1706 | SYSCTL_DECL(_kern_vnguard); | |
1707 | SYSCTL_NODE(_kern, OID_AUTO, vnguard, CTLFLAG_RW | CTLFLAG_LOCKED, 0, "vnguard"); | |
1708 | SYSCTL_INT(_kern_vnguard, OID_AUTO, flags, CTLFLAG_RW | CTLFLAG_LOCKED, | |
0a7de745 | 1709 | &vng_policy_flags, 0, "vnguard policy flags"); |
5ba3f43e A |
1710 | #endif |
1711 | ||
1712 | #endif /* CONFIG_MACF && CONFIG_VNGUARD */ |