xref: /freebsd/sys/security/mac_grantbylabel/mac_grantbylabel.c (revision 3a56015a2f5d630910177fa79a522bb95511ccf7)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2018-2023, Juniper Networks, Inc.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <sys/cdefs.h>
30 
31 #include "opt_mac.h"
32 
33 #include <sys/param.h>
34 #include <sys/capsicum.h>
35 #include <sys/proc.h>
36 #include <sys/vnode.h>
37 #include <sys/kernel.h>
38 #include <sys/module.h>
39 #include <sys/mac.h>
40 #include <sys/namei.h>
41 #include <sys/priv.h>
42 #include <sys/imgact.h>
43 #include <sys/sysctl.h>
44 #include <sys/syslog.h>
45 #include <security/mac/mac_policy.h>
46 
47 #include "mac_grantbylabel.h"
48 #include <security/mac_veriexec/mac_veriexec_internal.h>
49 
50 #define MAC_GRANTBYLABEL_FULLNAME   "MAC/grantbylabel"
51 
52 SYSCTL_NODE(_security_mac, OID_AUTO, grantbylabel, CTLFLAG_RW, 0,
53     "MAC/grantbylabel policy controls");
54 
55 #ifdef MAC_DEBUG
56 static int mac_grantbylabel_debug;
57 
58 SYSCTL_INT(_security_mac_grantbylabel, OID_AUTO, debug, CTLFLAG_RW,
59     &mac_grantbylabel_debug, 0, "Debug mac_grantbylabel");
60 
61 #define GRANTBYLABEL_DEBUG(n, x) if (mac_grantbylabel_debug >= (n)) printf x
62 
63 #define	MAC_GRANTBYLABEL_DBG(_lvl, _fmt, ...)				\
64 	do {								\
65 		GRANTBYLABEL_DEBUG((_lvl), (MAC_GRANTBYLABEL_FULLNAME ": " \
66 			_fmt "\n", ##__VA_ARGS__));			\
67 	} while(0)
68 #else
69 #define	MAC_GRANTBYLABEL_DBG(_lvl, _fmt, ...)
70 #endif
71 
72 
73 /* label token prefix */
74 #define GBL_PREFIX "gbl/"
75 
76 static int mac_grantbylabel_slot;
77 
78 #define	SLOT(l) \
79 	mac_label_get((l), mac_grantbylabel_slot)
80 #define	SLOT_SET(l, v) \
81 	mac_label_set((l), mac_grantbylabel_slot, (v))
82 
83 
84 /**
85  * @brief parse label into bitmask
86  *
87  * We are only interested in tokens prefixed by GBL_PREFIX ("gbl/").
88  *
89  * @return 32bit mask
90  */
91 static gbl_label_t
92 gbl_parse_label(const char *label)
93 {
94 	gbl_label_t gbl;
95 	char *cp;
96 
97 	if (!(label && *label))
98 		return GBL_EMPTY;
99 	gbl = 0;
100 	for (cp = strstr(label, GBL_PREFIX); cp; cp = strstr(cp, GBL_PREFIX)) {
101 		/* check we didn't find "fugbl/" */
102 		if (cp > label && cp[-1] != ',') {
103 			cp += sizeof(GBL_PREFIX);
104 			continue;
105 		}
106 		cp += sizeof(GBL_PREFIX) - 1;
107 		switch (*cp) {
108 		case 'b':
109 			if (strncmp(cp, "bind", 4) == 0)
110 				gbl |= GBL_BIND;
111 			break;
112 		case 'd':
113 			if (strncmp(cp, "daemon", 6) == 0)
114 				gbl |= (GBL_BIND|GBL_IPC|GBL_NET|GBL_PROC|
115 				    GBL_SYSCTL|GBL_VACCESS);
116 			break;
117 		case 'i':
118 			if (strncmp(cp, "ipc", 3) == 0)
119 				gbl |= GBL_IPC;
120 			break;
121 		case 'k':
122 			if (strncmp(cp, "kmem", 4) == 0)
123 				gbl |= GBL_KMEM;
124 			break;
125 		case 'n':
126 			if (strncmp(cp, "net", 3) == 0)
127 				gbl |= GBL_NET;
128 			break;
129 		case 'p':
130 			if (strncmp(cp, "proc", 4) == 0)
131 				gbl |= GBL_PROC;
132 			break;
133 		case 'r':
134 			if (strncmp(cp, "rtsock", 6) == 0)
135 				gbl |= GBL_RTSOCK;
136 			break;
137 		case 's':
138 			if (strncmp(cp, "sysctl", 6) == 0)
139 				gbl |= GBL_SYSCTL;
140 			break;
141 		case 'v':
142 			if (strncmp(cp, "vaccess", 7) == 0)
143 				gbl |= GBL_VACCESS;
144 			else if (strncmp(cp, "veriexec", 8) == 0)
145 				gbl |= GBL_VERIEXEC;
146 			break;
147 		default:		/* ignore unknown? */
148 			MAC_GRANTBYLABEL_DBG(1,
149 			    "ignoring unknown token at %s/%s",
150 			    GBL_PREFIX, cp);
151 			break;
152 		}
153 	}
154 
155 	return gbl;
156 }
157 
158 
159 /**
160  * @brief get the v_label for a vnode
161  *
162  * Lookup the label if not already set in v_label
163  *
164  * @return 32bit mask or 0 on error
165  */
166 static gbl_label_t
167 gbl_get_vlabel(struct vnode *vp, struct ucred *cred)
168 {
169 	struct vattr va;
170 	const char *label;
171 	gbl_label_t gbl;
172 	int error;
173 
174 	gbl = SLOT(vp->v_label);
175 	if (gbl == 0) {
176 		error = VOP_GETATTR(vp, &va, cred);
177 		if (error == 0) {
178 			label = mac_veriexec_metadata_get_file_label(va.va_fsid,
179 			    va.va_fileid, va.va_gen, FALSE);
180 			if (label) {
181 				MAC_GRANTBYLABEL_DBG(1,
182 				    "label=%s dev=%ju, file %ju.%lu",
183 				    label,
184 				    (uintmax_t)va.va_fsid,
185 				    (uintmax_t)va.va_fileid,
186 				    va.va_gen);
187 				gbl = gbl_parse_label(label);
188 			} else {
189 				gbl = GBL_EMPTY;
190 				MAC_GRANTBYLABEL_DBG(2, "no label dev=%ju, file %ju.%lu",
191 				    (uintmax_t)va.va_fsid,
192 				    (uintmax_t)va.va_fileid,
193 				    va.va_gen);
194 			}
195 		}
196 	}
197 	return gbl;
198 }
199 
200 
201 /**
202  * @brief grant priv if warranted
203  *
204  * If the cred is root, we have nothing to do.
205  * Otherwise see if the current process has a label
206  * that grants it the requested priv.
207  */
208 static int
209 mac_grantbylabel_priv_grant(struct ucred *cred, int priv)
210 {
211 	gbl_label_t label;
212 	int rc;
213 
214 	rc = EPERM;			/* default response */
215 
216 	if ((curproc->p_flag & (P_KPROC|P_SYSTEM)))
217 		return rc;		/* not interested */
218 
219 	switch (priv) {
220 	case PRIV_PROC_MEM_WRITE:
221 	case PRIV_KMEM_READ:
222 	case PRIV_KMEM_WRITE:
223 		break;
224 	case PRIV_VERIEXEC_DIRECT:
225 	case PRIV_VERIEXEC_NOVERIFY:
226 		/* XXX might want to skip in FIPS mode */
227 		break;
228 	default:
229 		if (cred->cr_uid == 0)
230 			return rc;	/* not interested */
231 		break;
232 	}
233 
234 	label = (gbl_label_t)(SLOT(curproc->p_textvp->v_label) |
235 	    SLOT(curproc->p_label));
236 
237 	/*
238 	 * We look at the extra privs granted
239 	 * via process label.
240 	 */
241 	switch (priv) {
242 	case PRIV_IPC_READ:
243 	case PRIV_IPC_WRITE:
244 		if (label & GBL_IPC)
245 			rc = 0;
246 		break;
247 	case PRIV_PROC_MEM_WRITE:
248 	case PRIV_KMEM_READ:
249 	case PRIV_KMEM_WRITE:
250 		if (label & GBL_KMEM)
251 			rc = 0;
252 		break;
253 	case PRIV_NETINET_BINDANY:
254 	case PRIV_NETINET_RESERVEDPORT:	/* socket bind low port */
255 	case PRIV_NETINET_REUSEPORT:
256 		if (label & GBL_BIND)
257 			rc = 0;
258 		break;
259 	case PRIV_NETINET_ADDRCTRL6:
260 	case PRIV_NET_LAGG:
261 	case PRIV_NET_SETIFFIB:
262 	case PRIV_NET_SETIFVNET:
263 	case PRIV_NETINET_SETHDROPTS:
264 	case PRIV_NET_VXLAN:
265 	case PRIV_NETINET_GETCRED:
266 	case PRIV_NETINET_IPSEC:
267 	case PRIV_NETINET_RAW:
268 		if (label & GBL_NET)
269 			rc = 0;
270 		break;
271 	case PRIV_NETINET_MROUTE:
272 	case PRIV_NET_ROUTE:
273 		if (label & GBL_RTSOCK)
274 			rc = 0;
275 		break;
276 	case PRIV_PROC_LIMIT:
277 	case PRIV_PROC_SETRLIMIT:
278 		if (label & GBL_PROC)
279 			rc = 0;
280 		break;
281 	case PRIV_SYSCTL_WRITE:
282 		if (label & GBL_SYSCTL)
283 			rc = 0;
284 		break;
285 	case PRIV_VFS_READ:
286 	case PRIV_VFS_WRITE:
287 		if (label & GBL_KMEM)
288 			rc = 0;
289 		/* FALLTHROUGH */
290 	case PRIV_VFS_ADMIN:
291 	case PRIV_VFS_BLOCKRESERVE:
292 	case PRIV_VFS_CHOWN:
293 	case PRIV_VFS_EXEC:	/* vaccess file and accmode & VEXEC */
294 	case PRIV_VFS_GENERATION:
295 	case PRIV_VFS_LOOKUP:	/* vaccess DIR */
296 		if (label & GBL_VACCESS)
297 			rc = 0;
298 		break;
299 	case PRIV_VERIEXEC_DIRECT:
300 		/*
301 		 * We are here because we are attempting to direct exec
302 		 * something with the 'indirect' flag set.
303 		 * We need to check parent label for this one.
304 		 */
305 		PROC_LOCK(curproc);
306 		label = (gbl_label_t)SLOT(curproc->p_pptr->p_textvp->v_label);
307 		if (label & GBL_VERIEXEC) {
308 			rc = 0;
309 			/*
310 			 * Of course the only reason to be running an
311 			 * interpreter this way is to bypass O_VERIFY
312 			 * so we can run unsigned script.
313 			 * We set GBL_VERIEXEC on p_label for
314 			 * PRIV_VERIEXEC_NOVERIFY below
315 			 */
316 			SLOT_SET(curproc->p_label, GBL_VERIEXEC);
317 		}
318 		PROC_UNLOCK(curproc);
319 		break;
320 	case PRIV_VERIEXEC_NOVERIFY:
321 		/* we look at p_label! see above */
322 		label = (gbl_label_t)SLOT(curproc->p_label);
323 		if (label & GBL_VERIEXEC)
324 			rc = 0;
325 		break;
326 	default:
327 		break;
328 	}
329 	MAC_GRANTBYLABEL_DBG(rc ? 1 : 2,
330 	    "pid=%d priv=%d, label=%#o rc=%d",
331 	    curproc->p_pid, priv, label, rc);
332 
333 	return rc;
334 }
335 
336 
337 /*
338  * If proc->p_textvp does not yet have a label,
339  * fetch file info from mac_veriexec
340  * and set label (if any) else set.
341  * If there is no label set it to GBL_EMPTY.
342  */
343 static int
344 mac_grantbylabel_proc_check_resource(struct ucred *cred,
345     struct proc *proc)
346 {
347 	gbl_label_t gbl;
348 
349 	if (!SLOT(proc->p_textvp->v_label)) {
350 		gbl = gbl_get_vlabel(proc->p_textvp, cred);
351 		if (gbl == 0)
352 			gbl = GBL_EMPTY;
353 		SLOT_SET(proc->p_textvp->v_label, gbl);
354 	}
355 	return 0;
356 }
357 
358 static int
359 mac_grantbylabel_syscall(struct thread *td, int call, void *arg)
360 {
361 	cap_rights_t rights;
362 	struct mac_grantbylabel_fetch_gbl_args gbl_args;
363 	struct file *fp;
364 	struct proc *proc;
365 	int error;
366 	int proc_locked;
367 
368 	switch (call) {
369 	case MAC_GRANTBYLABEL_FETCH_GBL:
370 	case MAC_GRANTBYLABEL_FETCH_PID_GBL:
371 		error = copyin(arg, &gbl_args, sizeof(gbl_args));
372 		if (error)
373 			return error;
374 		gbl_args.gbl = 0;
375 		break;
376 	default:
377 		return EOPNOTSUPP;
378 		break;
379 	}
380 	proc_locked = 0;
381 	switch (call) {
382 	case MAC_GRANTBYLABEL_FETCH_GBL:
383 		error = getvnode(td, gbl_args.u.fd,
384 		    cap_rights_init(&rights), &fp);
385 		if (error)
386 			return (error);
387 
388 		if (fp->f_type != DTYPE_VNODE) {
389 		       error = EINVAL;
390 		       goto cleanup_file;
391 		}
392 
393 		vn_lock(fp->f_vnode, LK_SHARED | LK_RETRY);
394 		gbl_args.gbl = gbl_get_vlabel(fp->f_vnode, td->td_ucred);
395 		if (gbl_args.gbl == 0)
396 			error = EOPNOTSUPP;
397 		else
398 			error = 0;
399 		VOP_UNLOCK(fp->f_vnode);
400 cleanup_file:
401 		fdrop(fp, td);
402 		break;
403 	case MAC_GRANTBYLABEL_FETCH_PID_GBL:
404 		error = 0;
405 		if (gbl_args.u.pid == 0
406 		    || gbl_args.u.pid == curproc->p_pid) {
407 			proc = curproc;
408 		} else {
409 			proc = pfind(gbl_args.u.pid);
410 			if (proc == NULL)
411 				return (EINVAL);
412 			proc_locked = 1;
413 		}
414 		gbl_args.gbl = (SLOT(proc->p_textvp->v_label) |
415 		    SLOT(proc->p_label));
416 		if (proc_locked)
417 			PROC_UNLOCK(proc);
418 		break;
419 	}
420 	if (error == 0) {
421 		error = copyout(&gbl_args, arg, sizeof(gbl_args));
422 	}
423 	return error;
424 }
425 
426 
427 static void
428 mac_grantbylabel_proc_init_label(struct label *label)
429 {
430 
431 	SLOT_SET(label, 0);		/* not yet set! */
432 }
433 
434 static void
435 mac_grantbylabel_vnode_init_label(struct label *label)
436 {
437 
438 	SLOT_SET(label, 0);		/* not yet set! */
439 }
440 
441 /**
442  * @brief set v_label if needed
443  */
444 static int
445 mac_grantbylabel_vnode_check_exec(struct ucred *cred __unused,
446     struct vnode *vp __unused, struct label *label __unused,
447     struct image_params *imgp, struct label *execlabel __unused)
448 {
449 	gbl_label_t gbl;
450 
451 	gbl = SLOT(vp->v_label);
452 	if (gbl == 0) {
453 		gbl = gbl_get_vlabel(vp, cred);
454 		if (gbl == 0)
455 			gbl = GBL_EMPTY;
456 		MAC_GRANTBYLABEL_DBG(1, "vnode_check_exec label=%#o", gbl);
457 		SLOT_SET(vp->v_label, gbl);
458 	}
459 	return 0;
460 }
461 
462 static void
463 mac_grantbylabel_copy_label(struct label *src, struct label *dest)
464 {
465 	SLOT_SET(dest, SLOT(src));
466 }
467 
468 /**
469  * @brief if interpreting copy script v_label to proc p_label
470  */
471 static int
472 mac_grantbylabel_vnode_execve_will_transition(struct ucred *old,
473     struct vnode *vp, struct label *vplabel,
474     struct label *interpvplabel, struct image_params *imgp,
475     struct label *execlabel)
476 {
477 	gbl_label_t gbl;
478 
479 	if (imgp->interpreted) {
480 		gbl = SLOT(interpvplabel);
481 		if (gbl) {
482 			SLOT_SET(imgp->proc->p_label, gbl);
483 		}
484 		MAC_GRANTBYLABEL_DBG(1, "execve_will_transition label=%#o", gbl);
485 	}
486 	return 0;
487 }
488 
489 
490 static struct mac_policy_ops mac_grantbylabel_ops =
491 {
492 	.mpo_proc_check_resource = mac_grantbylabel_proc_check_resource,
493 	.mpo_priv_grant = mac_grantbylabel_priv_grant,
494 	.mpo_syscall = mac_grantbylabel_syscall,
495 	.mpo_proc_init_label = mac_grantbylabel_proc_init_label,
496 	.mpo_vnode_check_exec = mac_grantbylabel_vnode_check_exec,
497 	.mpo_vnode_copy_label = mac_grantbylabel_copy_label,
498 	.mpo_vnode_execve_will_transition = mac_grantbylabel_vnode_execve_will_transition,
499 	.mpo_vnode_init_label = mac_grantbylabel_vnode_init_label,
500 };
501 
502 MAC_POLICY_SET(&mac_grantbylabel_ops, mac_grantbylabel,
503     MAC_GRANTBYLABEL_FULLNAME,
504     MPC_LOADTIME_FLAG_NOTLATE, &mac_grantbylabel_slot);
505 MODULE_VERSION(mac_grantbylabel, 1);
506 MODULE_DEPEND(mac_grantbylabel, mac_veriexec, MAC_VERIEXEC_VERSION,
507     MAC_VERIEXEC_VERSION, MAC_VERIEXEC_VERSION);
508