xref: /freebsd/sys/security/mac_do/mac_do.c (revision 11eb32958f2c6e70892201982c1c92a0140d6864)
1 /*-
2  * Copyright(c) 2024 Baptiste Daroussin <bapt@FreeBSD.org>
3  *
4  * SPDX-License-Identifier: BSD-2-Clause
5  */
6 
7 #include <sys/param.h>
8 #include <sys/systm.h>
9 #include <sys/jail.h>
10 #include <sys/kernel.h>
11 #include <sys/lock.h>
12 #include <sys/malloc.h>
13 #include <sys/module.h>
14 #include <sys/mount.h>
15 #include <sys/mutex.h>
16 #include <sys/priv.h>
17 #include <sys/proc.h>
18 #include <sys/socket.h>
19 #include <sys/sx.h>
20 #include <sys/sysctl.h>
21 #include <sys/ucred.h>
22 #include <sys/vnode.h>
23 
24 #include <security/mac/mac_policy.h>
25 
26 static SYSCTL_NODE(_security_mac, OID_AUTO, do,
27     CTLFLAG_RW|CTLFLAG_MPSAFE, 0, "mac_do policy controls");
28 
29 static int	do_enabled = 1;
30 SYSCTL_INT(_security_mac_do, OID_AUTO, enabled, CTLFLAG_RWTUN,
31     &do_enabled, 0, "Enforce do policy");
32 
33 static MALLOC_DEFINE(M_DO, "do_rule", "Rules for mac_do");
34 
35 #define MAC_RULE_STRING_LEN	1024
36 
37 static unsigned		mac_do_osd_jail_slot;
38 
39 #define RULE_UID	1
40 #define RULE_GID	2
41 #define RULE_ANY	3
42 
43 struct rule {
44 	int	from_type;
45 	union {
46 		uid_t f_uid;
47 		gid_t f_gid;
48 	};
49 	int	to_type;
50 	uid_t t_uid;
51 	TAILQ_ENTRY(rule) r_entries;
52 };
53 
54 struct rules {
55 	char string[MAC_RULE_STRING_LEN];
56 	TAILQ_HEAD(rulehead, rule) head;
57 };
58 
59 static void
60 toast_rules(struct rules *const rules)
61 {
62 	struct rulehead *const head = &rules->head;
63 	struct rule *rule;
64 
65 	while ((rule = TAILQ_FIRST(head)) != NULL) {
66 		TAILQ_REMOVE(head, rule, r_entries);
67 		free(rule, M_DO);
68 	}
69 	free(rules, M_DO);
70 }
71 
72 static struct rules *
73 alloc_rules(void)
74 {
75 	struct rules *const rules = malloc(sizeof(*rules), M_DO, M_WAITOK);
76 
77 	_Static_assert(MAC_RULE_STRING_LEN > 0, "MAC_RULE_STRING_LEN <= 0!");
78 	rules->string[0] = 0;
79 	TAILQ_INIT(&rules->head);
80 	return (rules);
81 }
82 
83 static int
84 parse_rule_element(char *element, struct rule **rule)
85 {
86 	int error = 0;
87 	char *type, *id, *p;
88 	struct rule *new;
89 
90 	new = malloc(sizeof(*new), M_DO, M_ZERO|M_WAITOK);
91 
92 	type = strsep(&element, "=");
93 	if (type == NULL) {
94 		error = EINVAL;
95 		goto error;
96 	}
97 	if (strcmp(type, "uid") == 0) {
98 		new->from_type = RULE_UID;
99 	} else if (strcmp(type, "gid") == 0) {
100 		new->from_type = RULE_GID;
101 	} else {
102 		error = EINVAL;
103 		goto error;
104 	}
105 	id = strsep(&element, ":");
106 	if (id == NULL) {
107 		error = EINVAL;
108 		goto error;
109 	}
110 	switch (new->from_type) {
111 	case RULE_UID:
112 		new->f_uid = strtol(id, &p, 10);
113 		break;
114 	case RULE_GID:
115 		new->f_gid = strtol(id, &p, 10);
116 		break;
117 	default:
118 		__assert_unreachable();
119 	}
120 	if (*p != '\0') {
121 		error = EINVAL;
122 		goto error;
123 	}
124 	if (element == NULL || *element == '\0') {
125 		error = EINVAL;
126 		goto error;
127 	}
128 	if (strcmp(element, "any") == 0 || strcmp(element, "*") == 0) {
129 		new->to_type = RULE_ANY;
130 	} else {
131 		new->to_type = RULE_UID;
132 		new->t_uid = strtol(element, &p, 10);
133 		if (*p != '\0') {
134 			error = EINVAL;
135 			goto error;
136 		}
137 	}
138 
139 	MPASS(error == 0);
140 	*rule = new;
141 	return (0);
142 error:
143 	MPASS(error != 0);
144 	free(new, M_DO);
145 	*rule = NULL;
146 	return (error);
147 }
148 
149 /*
150  * Parse rules specification and produce rule structures out of it.
151  *
152  * Returns 0 on success, with '*rulesp' made to point to a 'struct rule'
153  * representing the rules.  On error, the returned value is non-zero and
154  * '*rulesp' is unchanged.  If 'string' has length greater or equal to
155  * MAC_RULE_STRING_LEN, ENAMETOOLONG is returned.  If it is not in the expected
156  * format (comma-separated list of clauses of the form "<type>=<val>:<target>",
157  * where <type> is "uid" or "gid", <val> an UID or GID (depending on <type>) and
158  * <target> is "*", "any" or some UID), EINVAL is returned.
159  */
160 static int
161 parse_rules(const char *const string, struct rules **const rulesp)
162 {
163 	const size_t len = strlen(string);
164 	char *copy;
165 	char *p;
166 	char *element;
167 	struct rules *rules;
168 	struct rule *new;
169 	int error = 0;
170 
171 	if (len >= MAC_RULE_STRING_LEN)
172 		return (ENAMETOOLONG);
173 
174 	rules = alloc_rules();
175 	bcopy(string, rules->string, len + 1);
176 	MPASS(rules->string[len] == '\0'); /* Catch some races. */
177 
178 	copy = malloc(len + 1, M_DO, M_WAITOK);
179 	bcopy(string, copy, len + 1);
180 	MPASS(copy[len] == '\0'); /* Catch some races. */
181 
182 	p = copy;
183 	while ((element = strsep(&p, ",")) != NULL) {
184 		if (element[0] == '\0')
185 			continue;
186 		error = parse_rule_element(element, &new);
187 		if (error != 0) {
188 			toast_rules(rules);
189 			goto out;
190 		}
191 		TAILQ_INSERT_TAIL(&rules->head, new, r_entries);
192 	}
193 
194 	*rulesp = rules;
195 out:
196 	free(copy, M_DO);
197 	return (error);
198 }
199 
200 /*
201  * Find rules applicable to the passed prison.
202  *
203  * Returns the applicable rules (and never NULL).  'pr' must be unlocked.
204  * 'aprp' is set to the (ancestor) prison holding these, and it must be unlocked
205  * once the caller is done accessing the rules.  '*aprp' is equal to 'pr' if and
206  * only if the current jail has its own set of rules.
207  */
208 static struct rules *
209 find_rules(struct prison *const pr, struct prison **const aprp)
210 {
211 	struct prison *cpr, *ppr;
212 	struct rules *rules;
213 
214 	cpr = pr;
215 	for (;;) {
216 		prison_lock(cpr);
217 		rules = osd_jail_get(cpr, mac_do_osd_jail_slot);
218 		if (rules != NULL)
219 			break;
220 		prison_unlock(cpr);
221 
222 		ppr = cpr->pr_parent;
223 		MPASS(ppr != NULL); /* prison0 always has rules. */
224 		cpr = ppr;
225 	}
226 	*aprp = cpr;
227 
228 	return (rules);
229 }
230 
231 /*
232  * OSD destructor for slot 'mac_do_osd_jail_slot'.
233  *
234  * Called with 'value' not NULL.
235  */
236 static void
237 dealloc_osd(void *const value)
238 {
239 	struct rules *const rules = value;
240 
241 	toast_rules(rules);
242 }
243 
244 /*
245  * Remove the rules specifically associated to a prison.
246  *
247  * In practice, this means that the rules become inherited (from the closest
248  * ascendant that has some).
249  *
250  * Destroys the 'mac_do_osd_jail_slot' slot of the passed jail.
251  */
252 static void
253 remove_rules(struct prison *const pr)
254 {
255 	prison_lock(pr);
256 	/* This calls destructor dealloc_osd(). */
257 	osd_jail_del(pr, mac_do_osd_jail_slot);
258 	prison_unlock(pr);
259 }
260 
261 /*
262  * Assign already built rules to a jail.
263  */
264 static void
265 set_rules(struct prison *const pr, struct rules *const rules)
266 {
267 	struct rules *old_rules;
268 	void **rsv;
269 
270 	rsv = osd_reserve(mac_do_osd_jail_slot);
271 
272 	prison_lock(pr);
273 	old_rules = osd_jail_get(pr, mac_do_osd_jail_slot);
274 	osd_jail_set_reserved(pr, mac_do_osd_jail_slot, rsv, rules);
275 	prison_unlock(pr);
276 	if (old_rules != NULL)
277 		toast_rules(old_rules);
278 }
279 
280 /*
281  * Assigns empty rules to a jail.
282  */
283 static void
284 set_empty_rules(struct prison *const pr)
285 {
286 	struct rules *const rules = alloc_rules();
287 
288 	set_rules(pr, rules);
289 }
290 
291 /*
292  * Parse a rules specification and assign them to a jail.
293  *
294  * Returns the same error code as parse_rules() (which see).
295  */
296 static int
297 parse_and_set_rules(struct prison *const pr, const char *rules_string)
298 {
299 	struct rules *rules;
300 	int error;
301 
302 	error = parse_rules(rules_string, &rules);
303 	if (error != 0)
304 		return (error);
305 	set_rules(pr, rules);
306 	return (0);
307 }
308 
309 static int
310 mac_do_sysctl_rules(SYSCTL_HANDLER_ARGS)
311 {
312 	char *const buf = malloc(MAC_RULE_STRING_LEN, M_DO, M_WAITOK);
313 	struct prison *const td_pr = req->td->td_ucred->cr_prison;
314 	struct prison *pr;
315 	struct rules *rules;
316 	int error;
317 
318 	rules = find_rules(td_pr, &pr);
319 	strlcpy(buf, rules->string, MAC_RULE_STRING_LEN);
320 	prison_unlock(pr);
321 
322 	error = sysctl_handle_string(oidp, buf, MAC_RULE_STRING_LEN, req);
323 	if (error != 0 || req->newptr == NULL)
324 		goto out;
325 
326 	/* Set our prison's rules, not that of the jail we inherited from. */
327 	error = parse_and_set_rules(td_pr, buf);
328 out:
329 	free(buf, M_DO);
330 	return (error);
331 }
332 
333 SYSCTL_PROC(_security_mac_do, OID_AUTO, rules,
334     CTLTYPE_STRING|CTLFLAG_RW|CTLFLAG_PRISON|CTLFLAG_MPSAFE,
335     0, 0, mac_do_sysctl_rules, "A",
336     "Rules");
337 
338 
339 SYSCTL_JAIL_PARAM_SYS_SUBNODE(mac, do, CTLFLAG_RW, "Jail MAC/do parameters");
340 SYSCTL_JAIL_PARAM_STRING(_mac_do, rules, CTLFLAG_RW, MAC_RULE_STRING_LEN,
341     "Jail MAC/do rules");
342 
343 
344 static int
345 mac_do_jail_create(void *obj, void *data __unused)
346 {
347 	struct prison *const pr = obj;
348 
349 	set_empty_rules(pr);
350 	return (0);
351 }
352 
353 static int
354 mac_do_jail_get(void *obj, void *data)
355 {
356 	struct prison *ppr, *const pr = obj;
357 	struct vfsoptlist *const opts = data;
358 	struct rules *rules;
359 	int jsys, error;
360 
361 	rules = find_rules(pr, &ppr);
362 
363 	jsys = pr == ppr ?
364 	    (TAILQ_EMPTY(&rules->head) ? JAIL_SYS_DISABLE : JAIL_SYS_NEW) :
365 	    JAIL_SYS_INHERIT;
366 	error = vfs_setopt(opts, "mac.do", &jsys, sizeof(jsys));
367 	if (error != 0 && error != ENOENT)
368 		goto done;
369 
370 	error = vfs_setopts(opts, "mac.do.rules", rules->string);
371 	if (error != 0 && error != ENOENT)
372 		goto done;
373 
374 	error = 0;
375 done:
376 	prison_unlock(ppr);
377 	return (error);
378 }
379 
380 /*
381  * -1 is used as a sentinel in mac_do_jail_check() and mac_do_jail_set() below.
382  */
383 _Static_assert(-1 != JAIL_SYS_DISABLE && -1 != JAIL_SYS_NEW &&
384     -1 != JAIL_SYS_INHERIT,
385     "mac_do(4) uses -1 as a sentinel for uninitialized 'jsys'.");
386 
387 /*
388  * We perform only cheap checks here, i.e., we do not really parse the rules
389  * specification string, if any.
390  */
391 static int
392 mac_do_jail_check(void *obj, void *data)
393 {
394 	struct vfsoptlist *opts = data;
395 	char *rules_string;
396 	int error, jsys, size;
397 
398 	error = vfs_copyopt(opts, "mac.do", &jsys, sizeof(jsys));
399 	if (error == ENOENT)
400 		jsys = -1;
401 	else {
402 		if (error != 0)
403 			return (error);
404 		if (jsys != JAIL_SYS_DISABLE && jsys != JAIL_SYS_NEW &&
405 		    jsys != JAIL_SYS_INHERIT)
406 			return (EINVAL);
407 	}
408 
409 	/*
410 	 * We use vfs_getopt() here instead of vfs_getopts() to get the length.
411 	 * We perform the additional checks done by the latter here, even if
412 	 * jail_set() calls vfs_getopts() itself later (they becoming
413 	 * inconsistent wouldn't cause any security problem).
414 	 */
415 	error = vfs_getopt(opts, "mac.do.rules", (void**)&rules_string, &size);
416 	if (error == ENOENT) {
417 		/*
418 		 * Default (in absence of "mac.do.rules") is to disable (and, in
419 		 * particular, not inherit).
420 		 */
421 		if (jsys == -1)
422 			jsys = JAIL_SYS_DISABLE;
423 
424 		if (jsys == JAIL_SYS_NEW) {
425 			vfs_opterror(opts, "'mac.do.rules' must be specified "
426 			    "given 'mac.do''s value");
427 			return (EINVAL);
428 		}
429 
430 		/* Absence of "mac.do.rules" at this point is OK. */
431 		error = 0;
432 	} else {
433 		if (error != 0)
434 			return (error);
435 
436 		/* Not a proper string. */
437 		if (size == 0 || rules_string[size - 1] != '\0') {
438 			vfs_opterror(opts, "'mac.do.rules' not a proper string");
439 			return (EINVAL);
440 		}
441 
442 		if (size > MAC_RULE_STRING_LEN) {
443 			vfs_opterror(opts, "'mdo.rules' too long");
444 			return (ENAMETOOLONG);
445 		}
446 
447 		if (jsys == -1)
448 			/* Default (if "mac.do.rules" is present). */
449 			jsys = rules_string[0] == '\0' ? JAIL_SYS_DISABLE :
450 			    JAIL_SYS_NEW;
451 
452 		/*
453 		 * Be liberal and accept JAIL_SYS_DISABLE and JAIL_SYS_INHERIT
454 		 * with an explicit empty rules specification.
455 		 */
456 		switch (jsys) {
457 		case JAIL_SYS_DISABLE:
458 		case JAIL_SYS_INHERIT:
459 			if (rules_string[0] != '\0') {
460 				vfs_opterror(opts, "'mac.do.rules' specified "
461 				    "but should not given 'mac.do''s value");
462 				return (EINVAL);
463 			}
464 			break;
465 		}
466 	}
467 
468 	return (error);
469 }
470 
471 static int
472 mac_do_jail_set(void *obj, void *data)
473 {
474 	struct prison *pr = obj;
475 	struct vfsoptlist *opts = data;
476 	char *rules_string;
477 	int error, jsys;
478 
479 	/*
480 	 * The invariants checks used below correspond to what has already been
481 	 * checked in jail_check() above.
482 	 */
483 
484 	error = vfs_copyopt(opts, "mac.do", &jsys, sizeof(jsys));
485 	MPASS(error == 0 || error == ENOENT);
486 	if (error != 0)
487 		jsys = -1; /* Mark unfilled. */
488 
489 	rules_string = vfs_getopts(opts, "mac.do.rules", &error);
490 	MPASS(error == 0 || error == ENOENT);
491 	if (error == 0) {
492 		MPASS(strlen(rules_string) < MAC_RULE_STRING_LEN);
493 		if (jsys == -1)
494 			/* Default (if "mac.do.rules" is present). */
495 			jsys = rules_string[0] == '\0' ? JAIL_SYS_DISABLE :
496 			    JAIL_SYS_NEW;
497 		else
498 			MPASS(jsys == JAIL_SYS_NEW ||
499 			    ((jsys == JAIL_SYS_DISABLE ||
500 			    jsys == JAIL_SYS_INHERIT) &&
501 			    rules_string[0] == '\0'));
502 	} else {
503 		MPASS(jsys != JAIL_SYS_NEW);
504 		if (jsys == -1)
505 			/*
506 			 * Default (in absence of "mac.do.rules") is to disable
507 			 * (and, in particular, not inherit).
508 			 */
509 			jsys = JAIL_SYS_DISABLE;
510 		/* If disabled, we'll store an empty rule specification. */
511 		if (jsys == JAIL_SYS_DISABLE)
512 			rules_string = "";
513 	}
514 
515 	switch (jsys) {
516 	case JAIL_SYS_INHERIT:
517 		remove_rules(pr);
518 		error = 0;
519 		break;
520 	case JAIL_SYS_DISABLE:
521 	case JAIL_SYS_NEW:
522 		error = parse_and_set_rules(pr, rules_string);
523 		break;
524 	default:
525 		__assert_unreachable();
526 	}
527 	return (error);
528 }
529 
530 /*
531  * OSD jail methods.
532  *
533  * There is no PR_METHOD_REMOVE, as OSD storage is destroyed by the common jail
534  * code (see prison_cleanup()), which triggers a run of our dealloc_osd()
535  * destructor.
536  */
537 static const osd_method_t osd_methods[PR_MAXMETHOD] = {
538 	[PR_METHOD_CREATE] = mac_do_jail_create,
539 	[PR_METHOD_GET] = mac_do_jail_get,
540 	[PR_METHOD_CHECK] = mac_do_jail_check,
541 	[PR_METHOD_SET] = mac_do_jail_set,
542 };
543 
544 
545 static void
546 mac_do_init(struct mac_policy_conf *mpc)
547 {
548 	struct prison *pr;
549 
550 	mac_do_osd_jail_slot = osd_jail_register(dealloc_osd, osd_methods);
551 	set_empty_rules(&prison0);
552 	sx_slock(&allprison_lock);
553 	TAILQ_FOREACH(pr, &allprison, pr_list)
554 	    set_empty_rules(pr);
555 	sx_sunlock(&allprison_lock);
556 }
557 
558 static void
559 mac_do_destroy(struct mac_policy_conf *mpc)
560 {
561 	osd_jail_deregister(mac_do_osd_jail_slot);
562 }
563 
564 static bool
565 rule_applies(struct ucred *cred, struct rule *r)
566 {
567 	if (r->from_type == RULE_UID && r->f_uid == cred->cr_uid)
568 		return (true);
569 	if (r->from_type == RULE_GID && groupmember(r->f_gid, cred))
570 		return (true);
571 	return (false);
572 }
573 
574 static int
575 mac_do_priv_grant(struct ucred *cred, int priv)
576 {
577 	struct rule *r;
578 	struct prison *pr;
579 	struct rules *rule;
580 
581 	if (do_enabled == 0)
582 		return (EPERM);
583 
584 	rule = find_rules(cred->cr_prison, &pr);
585 	TAILQ_FOREACH(r, &rule->head, r_entries) {
586 		if (rule_applies(cred, r)) {
587 			switch (priv) {
588 			case PRIV_CRED_SETGROUPS:
589 			case PRIV_CRED_SETUID:
590 				prison_unlock(pr);
591 				return (0);
592 			default:
593 				break;
594 			}
595 		}
596 	}
597 	prison_unlock(pr);
598 	return (EPERM);
599 }
600 
601 static int
602 mac_do_check_setgroups(struct ucred *cred, int ngrp, gid_t *groups)
603 {
604 	struct rule *r;
605 	char *fullpath = NULL;
606 	char *freebuf = NULL;
607 	struct prison *pr;
608 	struct rules *rule;
609 
610 	if (do_enabled == 0)
611 		return (0);
612 	if (cred->cr_uid == 0)
613 		return (0);
614 
615 	if (vn_fullpath(curproc->p_textvp, &fullpath, &freebuf) != 0)
616 		return (EPERM);
617 	if (strcmp(fullpath, "/usr/bin/mdo") != 0) {
618 		free(freebuf, M_TEMP);
619 		return (EPERM);
620 	}
621 	free(freebuf, M_TEMP);
622 
623 	rule = find_rules(cred->cr_prison, &pr);
624 	TAILQ_FOREACH(r, &rule->head, r_entries) {
625 		if (rule_applies(cred, r)) {
626 			prison_unlock(pr);
627 			return (0);
628 		}
629 	}
630 	prison_unlock(pr);
631 
632 	return (EPERM);
633 }
634 
635 static int
636 mac_do_check_setuid(struct ucred *cred, uid_t uid)
637 {
638 	struct rule *r;
639 	int error;
640 	char *fullpath = NULL;
641 	char *freebuf = NULL;
642 	struct prison *pr;
643 	struct rules *rule;
644 
645 	if (do_enabled == 0)
646 		return (0);
647 	if (cred->cr_uid == uid || cred->cr_uid == 0 || cred->cr_ruid == 0)
648 		return (0);
649 
650 	if (vn_fullpath(curproc->p_textvp, &fullpath, &freebuf) != 0)
651 		return (EPERM);
652 	if (strcmp(fullpath, "/usr/bin/mdo") != 0) {
653 		free(freebuf, M_TEMP);
654 		return (EPERM);
655 	}
656 	free(freebuf, M_TEMP);
657 
658 	error = EPERM;
659 	rule = find_rules(cred->cr_prison, &pr);
660 	TAILQ_FOREACH(r, &rule->head, r_entries) {
661 		if (r->from_type == RULE_UID) {
662 			if (cred->cr_uid != r->f_uid)
663 				continue;
664 			if (r->to_type == RULE_ANY) {
665 				error = 0;
666 				break;
667 			}
668 			if (r->to_type == RULE_UID && uid == r->t_uid) {
669 				error = 0;
670 				break;
671 			}
672 		}
673 		if (r->from_type == RULE_GID) {
674 			if (!groupmember(r->f_gid, cred))
675 				continue;
676 			if (r->to_type == RULE_ANY) {
677 				error = 0;
678 				break;
679 			}
680 			if (r->to_type == RULE_UID && uid == r->t_uid) {
681 				error = 0;
682 				break;
683 			}
684 		}
685 	}
686 	prison_unlock(pr);
687 	return (error);
688 }
689 
690 static struct mac_policy_ops do_ops = {
691 	.mpo_destroy = mac_do_destroy,
692 	.mpo_init = mac_do_init,
693 	.mpo_cred_check_setuid = mac_do_check_setuid,
694 	.mpo_cred_check_setgroups = mac_do_check_setgroups,
695 	.mpo_priv_grant = mac_do_priv_grant,
696 };
697 
698 MAC_POLICY_SET(&do_ops, mac_do, "MAC/do",
699    MPC_LOADTIME_FLAG_UNLOADOK, NULL);
700 MODULE_VERSION(mac_do, 1);
701