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