xref: /freebsd/sys/security/mac_do/mac_do.c (revision 2a20ce91dc29e5a80f4eeb9352cf3169cd1891b9)
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 static int
381 mac_do_jail_check(void *obj, void *data)
382 {
383 	struct vfsoptlist *opts = data;
384 	char *rules_string;
385 	int error, jsys, len;
386 
387 	error = vfs_copyopt(opts, "mac.do", &jsys, sizeof(jsys));
388 	if (error != ENOENT) {
389 		if (error != 0)
390 			return (error);
391 		if (jsys != JAIL_SYS_NEW && jsys != JAIL_SYS_INHERIT)
392 			return (EINVAL);
393 	}
394 	error = vfs_getopt(opts, "mac.do.rules", (void **)&rules_string, &len);
395 	if (error != ENOENT) {
396 		if (error != 0)
397 			return (error);
398 		if (len > MAC_RULE_STRING_LEN) {
399 			vfs_opterror(opts, "mdo.rules too long");
400 			return (ENAMETOOLONG);
401 		}
402 	}
403 	if (error == ENOENT)
404 		error = 0;
405 	return (error);
406 }
407 
408 static int
409 mac_do_jail_set(void *obj, void *data)
410 {
411 	struct prison *pr = obj;
412 	struct vfsoptlist *opts = data;
413 	char *rules_string;
414 	int error, jsys, len;
415 
416 	error = vfs_copyopt(opts, "mac.do", &jsys, sizeof(jsys));
417 	if (error == ENOENT)
418 		jsys = -1;
419 	error = vfs_getopt(opts, "mac.do.rules", (void **)&rules_string, &len);
420 	if (error == ENOENT)
421 		rules_string = "";
422 	else
423 		jsys = JAIL_SYS_NEW;
424 	switch (jsys) {
425 	case JAIL_SYS_INHERIT:
426 		remove_rules(pr);
427 		error = 0;
428 		break;
429 	case JAIL_SYS_NEW:
430 		error = parse_and_set_rules(pr, rules_string);
431 		break;
432 	}
433 	return (error);
434 }
435 
436 /*
437  * OSD jail methods.
438  *
439  * There is no PR_METHOD_REMOVE, as OSD storage is destroyed by the common jail
440  * code (see prison_cleanup()), which triggers a run of our dealloc_osd()
441  * destructor.
442  */
443 static const osd_method_t osd_methods[PR_MAXMETHOD] = {
444 	[PR_METHOD_CREATE] = mac_do_jail_create,
445 	[PR_METHOD_GET] = mac_do_jail_get,
446 	[PR_METHOD_CHECK] = mac_do_jail_check,
447 	[PR_METHOD_SET] = mac_do_jail_set,
448 };
449 
450 
451 static void
452 mac_do_init(struct mac_policy_conf *mpc)
453 {
454 	struct prison *pr;
455 
456 	mac_do_osd_jail_slot = osd_jail_register(dealloc_osd, osd_methods);
457 	set_empty_rules(&prison0);
458 	sx_slock(&allprison_lock);
459 	TAILQ_FOREACH(pr, &allprison, pr_list)
460 	    set_empty_rules(pr);
461 	sx_sunlock(&allprison_lock);
462 }
463 
464 static void
465 mac_do_destroy(struct mac_policy_conf *mpc)
466 {
467 	osd_jail_deregister(mac_do_osd_jail_slot);
468 }
469 
470 static bool
471 rule_applies(struct ucred *cred, struct rule *r)
472 {
473 	if (r->from_type == RULE_UID && r->f_uid == cred->cr_uid)
474 		return (true);
475 	if (r->from_type == RULE_GID && groupmember(r->f_gid, cred))
476 		return (true);
477 	return (false);
478 }
479 
480 static int
481 mac_do_priv_grant(struct ucred *cred, int priv)
482 {
483 	struct rule *r;
484 	struct prison *pr;
485 	struct rules *rule;
486 
487 	if (do_enabled == 0)
488 		return (EPERM);
489 
490 	rule = find_rules(cred->cr_prison, &pr);
491 	TAILQ_FOREACH(r, &rule->head, r_entries) {
492 		if (rule_applies(cred, r)) {
493 			switch (priv) {
494 			case PRIV_CRED_SETGROUPS:
495 			case PRIV_CRED_SETUID:
496 				prison_unlock(pr);
497 				return (0);
498 			default:
499 				break;
500 			}
501 		}
502 	}
503 	prison_unlock(pr);
504 	return (EPERM);
505 }
506 
507 static int
508 mac_do_check_setgroups(struct ucred *cred, int ngrp, gid_t *groups)
509 {
510 	struct rule *r;
511 	char *fullpath = NULL;
512 	char *freebuf = NULL;
513 	struct prison *pr;
514 	struct rules *rule;
515 
516 	if (do_enabled == 0)
517 		return (0);
518 	if (cred->cr_uid == 0)
519 		return (0);
520 
521 	if (vn_fullpath(curproc->p_textvp, &fullpath, &freebuf) != 0)
522 		return (EPERM);
523 	if (strcmp(fullpath, "/usr/bin/mdo") != 0) {
524 		free(freebuf, M_TEMP);
525 		return (EPERM);
526 	}
527 	free(freebuf, M_TEMP);
528 
529 	rule = find_rules(cred->cr_prison, &pr);
530 	TAILQ_FOREACH(r, &rule->head, r_entries) {
531 		if (rule_applies(cred, r)) {
532 			prison_unlock(pr);
533 			return (0);
534 		}
535 	}
536 	prison_unlock(pr);
537 
538 	return (EPERM);
539 }
540 
541 static int
542 mac_do_check_setuid(struct ucred *cred, uid_t uid)
543 {
544 	struct rule *r;
545 	int error;
546 	char *fullpath = NULL;
547 	char *freebuf = NULL;
548 	struct prison *pr;
549 	struct rules *rule;
550 
551 	if (do_enabled == 0)
552 		return (0);
553 	if (cred->cr_uid == uid || cred->cr_uid == 0 || cred->cr_ruid == 0)
554 		return (0);
555 
556 	if (vn_fullpath(curproc->p_textvp, &fullpath, &freebuf) != 0)
557 		return (EPERM);
558 	if (strcmp(fullpath, "/usr/bin/mdo") != 0) {
559 		free(freebuf, M_TEMP);
560 		return (EPERM);
561 	}
562 	free(freebuf, M_TEMP);
563 
564 	error = EPERM;
565 	rule = find_rules(cred->cr_prison, &pr);
566 	TAILQ_FOREACH(r, &rule->head, r_entries) {
567 		if (r->from_type == RULE_UID) {
568 			if (cred->cr_uid != r->f_uid)
569 				continue;
570 			if (r->to_type == RULE_ANY) {
571 				error = 0;
572 				break;
573 			}
574 			if (r->to_type == RULE_UID && uid == r->t_uid) {
575 				error = 0;
576 				break;
577 			}
578 		}
579 		if (r->from_type == RULE_GID) {
580 			if (!groupmember(r->f_gid, cred))
581 				continue;
582 			if (r->to_type == RULE_ANY) {
583 				error = 0;
584 				break;
585 			}
586 			if (r->to_type == RULE_UID && uid == r->t_uid) {
587 				error = 0;
588 				break;
589 			}
590 		}
591 	}
592 	prison_unlock(pr);
593 	return (error);
594 }
595 
596 static struct mac_policy_ops do_ops = {
597 	.mpo_destroy = mac_do_destroy,
598 	.mpo_init = mac_do_init,
599 	.mpo_cred_check_setuid = mac_do_check_setuid,
600 	.mpo_cred_check_setgroups = mac_do_check_setgroups,
601 	.mpo_priv_grant = mac_do_priv_grant,
602 };
603 
604 MAC_POLICY_SET(&do_ops, mac_do, "MAC/do",
605    MPC_LOADTIME_FLAG_UNLOADOK, NULL);
606 MODULE_VERSION(mac_do, 1);
607