xref: /freebsd/sys/security/mac_do/mac_do.c (revision 2b2c19b7f697cc88d4da3e8e13051139cd0a4f96)
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 SYSCTL_DECL(_security_mac);
27 
28 static SYSCTL_NODE(_security_mac, OID_AUTO, do,
29     CTLFLAG_RW|CTLFLAG_MPSAFE, 0, "mac_do policy controls");
30 
31 static int	do_enabled = 1;
32 SYSCTL_INT(_security_mac_do, OID_AUTO, enabled, CTLFLAG_RWTUN,
33     &do_enabled, 0, "Enforce do policy");
34 
35 static MALLOC_DEFINE(M_DO, "do_rule", "Rules for mac_do");
36 
37 #define MAC_RULE_STRING_LEN	1024
38 
39 static unsigned		mac_do_osd_jail_slot;
40 
41 #define RULE_UID	1
42 #define RULE_GID	2
43 #define RULE_ANY	3
44 
45 struct rule {
46 	int	from_type;
47 	union {
48 		uid_t f_uid;
49 		gid_t f_gid;
50 	};
51 	int	to_type;
52 	uid_t t_uid;
53 	TAILQ_ENTRY(rule) r_entries;
54 };
55 
56 struct rules {
57 	char string[MAC_RULE_STRING_LEN];
58 	TAILQ_HEAD(rulehead, rule) head;
59 };
60 
61 static void
62 toast_rules(struct rules *const rules)
63 {
64 	struct rulehead *const head = &rules->head;
65 	struct rule *rule;
66 
67 	while ((rule = TAILQ_FIRST(head)) != NULL) {
68 		TAILQ_REMOVE(head, rule, r_entries);
69 		free(rule, M_DO);
70 	}
71 	free(rules, M_DO);
72 }
73 
74 static struct rules *
75 alloc_rules(void)
76 {
77 	struct rules *const rules = malloc(sizeof(*rules), M_DO, M_WAITOK);
78 
79 	_Static_assert(MAC_RULE_STRING_LEN > 0, "MAC_RULE_STRING_LEN <= 0!");
80 	rules->string[0] = 0;
81 	TAILQ_INIT(&rules->head);
82 	return (rules);
83 }
84 
85 static int
86 parse_rule_element(char *element, struct rule **rule)
87 {
88 	int error = 0;
89 	char *type, *id, *p;
90 	struct rule *new;
91 
92 	new = malloc(sizeof(*new), M_DO, M_ZERO|M_WAITOK);
93 
94 	type = strsep(&element, "=");
95 	if (type == NULL) {
96 		error = EINVAL;
97 		goto error;
98 	}
99 	if (strcmp(type, "uid") == 0) {
100 		new->from_type = RULE_UID;
101 	} else if (strcmp(type, "gid") == 0) {
102 		new->from_type = RULE_GID;
103 	} else {
104 		error = EINVAL;
105 		goto error;
106 	}
107 	id = strsep(&element, ":");
108 	if (id == NULL) {
109 		error = EINVAL;
110 		goto error;
111 	}
112 	switch (new->from_type) {
113 	case RULE_UID:
114 		new->f_uid = strtol(id, &p, 10);
115 		break;
116 	case RULE_GID:
117 		new->f_gid = strtol(id, &p, 10);
118 		break;
119 	default:
120 		__assert_unreachable();
121 	}
122 	if (*p != '\0') {
123 		error = EINVAL;
124 		goto error;
125 	}
126 	if (element == NULL || *element == '\0') {
127 		error = EINVAL;
128 		goto error;
129 	}
130 	if (strcmp(element, "any") == 0 || strcmp(element, "*") == 0) {
131 		new->to_type = RULE_ANY;
132 	} else {
133 		new->to_type = RULE_UID;
134 		new->t_uid = strtol(element, &p, 10);
135 		if (*p != '\0') {
136 			error = EINVAL;
137 			goto error;
138 		}
139 	}
140 
141 	MPASS(error == 0);
142 	*rule = new;
143 	return (0);
144 error:
145 	MPASS(error != 0);
146 	free(new, M_DO);
147 	*rule = NULL;
148 	return (error);
149 }
150 
151 /*
152  * Parse rules specification and produce rule structures out of it.
153  *
154  * Returns 0 on success, with '*rulesp' made to point to a 'struct rule'
155  * representing the rules.  On error, the returned value is non-zero and
156  * '*rulesp' is unchanged.  If 'string' has length greater or equal to
157  * MAC_RULE_STRING_LEN, ENAMETOOLONG is returned.  If it is not in the expected
158  * format (comma-separated list of clauses of the form "<type>=<val>:<target>",
159  * where <type> is "uid" or "gid", <val> an UID or GID (depending on <type>) and
160  * <target> is "*", "any" or some UID), EINVAL is returned.
161  */
162 static int
163 parse_rules(const char *const string, struct rules **const rulesp)
164 {
165 	const size_t len = strlen(string);
166 	char *copy;
167 	char *p;
168 	char *element;
169 	struct rules *rules;
170 	struct rule *new;
171 	int error = 0;
172 
173 	if (len >= MAC_RULE_STRING_LEN)
174 		return (ENAMETOOLONG);
175 
176 	rules = alloc_rules();
177 	bcopy(string, rules->string, len + 1);
178 	MPASS(rules->string[len] == '\0'); /* Catch some races. */
179 
180 	copy = malloc(len + 1, M_DO, M_WAITOK);
181 	bcopy(string, copy, len + 1);
182 	MPASS(copy[len] == '\0'); /* Catch some races. */
183 
184 	p = copy;
185 	while ((element = strsep(&p, ",")) != NULL) {
186 		if (element[0] == '\0')
187 			continue;
188 		error = parse_rule_element(element, &new);
189 		if (error != 0) {
190 			toast_rules(rules);
191 			goto out;
192 		}
193 		TAILQ_INSERT_TAIL(&rules->head, new, r_entries);
194 	}
195 
196 	*rulesp = rules;
197 out:
198 	free(copy, M_DO);
199 	return (error);
200 }
201 
202 /*
203  * Find rules applicable to the passed prison.
204  *
205  * Returns the applicable rules (and never NULL).  'pr' must be unlocked.
206  * 'aprp' is set to the (ancestor) prison holding these, and it must be unlocked
207  * once the caller is done accessing the rules.  '*aprp' is equal to 'pr' if and
208  * only if the current jail has its own set of rules.
209  */
210 static struct rules *
211 find_rules(struct prison *const pr, struct prison **const aprp)
212 {
213 	struct prison *cpr, *ppr;
214 	struct rules *rules;
215 
216 	cpr = pr;
217 	for (;;) {
218 		prison_lock(cpr);
219 		rules = osd_jail_get(cpr, mac_do_osd_jail_slot);
220 		if (rules != NULL)
221 			break;
222 		prison_unlock(cpr);
223 
224 		ppr = cpr->pr_parent;
225 		MPASS(ppr != NULL); /* prison0 always has rules. */
226 		cpr = ppr;
227 	}
228 	*aprp = cpr;
229 
230 	return (rules);
231 }
232 
233 /*
234  * OSD destructor for slot 'mac_do_osd_jail_slot'.
235  *
236  * Called with 'value' not NULL.
237  */
238 static void
239 dealloc_osd(void *const value)
240 {
241 	struct rules *const rules = value;
242 
243 	toast_rules(rules);
244 }
245 
246 /*
247  * Remove the rules specifically associated to a prison.
248  *
249  * In practice, this means that the rules become inherited (from the closest
250  * ascendant that has some).
251  *
252  * Destroys the 'mac_do_osd_jail_slot' slot of the passed jail.
253  */
254 static void
255 remove_rules(struct prison *const pr)
256 {
257 	prison_lock(pr);
258 	/* This calls destructor dealloc_osd(). */
259 	osd_jail_del(pr, mac_do_osd_jail_slot);
260 	prison_unlock(pr);
261 }
262 
263 /*
264  * Assign already built rules to a jail.
265  */
266 static void
267 set_rules(struct prison *const pr, struct rules *const rules)
268 {
269 	struct rules *old_rules;
270 	void **rsv;
271 
272 	rsv = osd_reserve(mac_do_osd_jail_slot);
273 
274 	prison_lock(pr);
275 	old_rules = osd_jail_get(pr, mac_do_osd_jail_slot);
276 	osd_jail_set_reserved(pr, mac_do_osd_jail_slot, rsv, rules);
277 	prison_unlock(pr);
278 	if (old_rules != NULL)
279 		toast_rules(old_rules);
280 }
281 
282 /*
283  * Assigns empty rules to a jail.
284  */
285 static void
286 set_empty_rules(struct prison *const pr)
287 {
288 	struct rules *const rules = alloc_rules();
289 
290 	set_rules(pr, rules);
291 }
292 
293 /*
294  * Parse a rules specification and assign them to a jail.
295  *
296  * Returns the same error code as parse_rules() (which see).
297  */
298 static int
299 parse_and_set_rules(struct prison *const pr, const char *rules_string)
300 {
301 	struct rules *rules;
302 	int error;
303 
304 	error = parse_rules(rules_string, &rules);
305 	if (error != 0)
306 		return (error);
307 	set_rules(pr, rules);
308 	return (0);
309 }
310 
311 static int
312 sysctl_rules(SYSCTL_HANDLER_ARGS)
313 {
314 	char *const buf = malloc(MAC_RULE_STRING_LEN, M_DO, M_WAITOK);
315 	struct prison *const td_pr = req->td->td_ucred->cr_prison;
316 	struct prison *pr;
317 	struct rules *rules;
318 	int error;
319 
320 	rules = find_rules(td_pr, &pr);
321 	strlcpy(buf, rules->string, MAC_RULE_STRING_LEN);
322 	prison_unlock(pr);
323 
324 	error = sysctl_handle_string(oidp, buf, MAC_RULE_STRING_LEN, req);
325 	if (error != 0 || req->newptr == NULL)
326 		goto out;
327 
328 	/* Set our prison's rules, not that of the jail we inherited from. */
329 	error = parse_and_set_rules(td_pr, buf);
330 out:
331 	free(buf, M_DO);
332 	return (error);
333 }
334 
335 SYSCTL_PROC(_security_mac_do, OID_AUTO, rules,
336     CTLTYPE_STRING|CTLFLAG_RW|CTLFLAG_PRISON|CTLFLAG_MPSAFE,
337     0, 0, sysctl_rules, "A",
338     "Rules");
339 
340 
341 SYSCTL_JAIL_PARAM_SYS_NODE(mdo, CTLFLAG_RW, "Jail MAC/do parameters");
342 SYSCTL_JAIL_PARAM_STRING(_mdo, rules, CTLFLAG_RW, MAC_RULE_STRING_LEN,
343     "Jail MAC/do rules");
344 
345 
346 static int
347 mac_do_jail_create(void *obj, void *data __unused)
348 {
349 	struct prison *const pr = obj;
350 
351 	set_empty_rules(pr);
352 	return (0);
353 }
354 
355 static int
356 mac_do_jail_get(void *obj, void *data)
357 {
358 	struct prison *ppr, *pr = obj;
359 	struct vfsoptlist *opts = data;
360 	struct rules *rules;
361 	int jsys, error;
362 
363 	rules = find_rules(pr, &ppr);
364 	error = vfs_setopt(opts, "mdo", &jsys, sizeof(jsys));
365 	if (error != 0 && error != ENOENT)
366 		goto done;
367 	error = vfs_setopts(opts, "mdo.rules", rules->string);
368 	if (error != 0 && error != ENOENT)
369 		goto done;
370 	prison_unlock(ppr);
371 	error = 0;
372 done:
373 	return (0);
374 }
375 
376 static int
377 mac_do_jail_check(void *obj, void *data)
378 {
379 	struct vfsoptlist *opts = data;
380 	char *rules_string;
381 	int error, jsys, len;
382 
383 	error = vfs_copyopt(opts, "mdo", &jsys, sizeof(jsys));
384 	if (error != ENOENT) {
385 		if (error != 0)
386 			return (error);
387 		if (jsys != JAIL_SYS_NEW && jsys != JAIL_SYS_INHERIT)
388 			return (EINVAL);
389 	}
390 	error = vfs_getopt(opts, "mdo.rules", (void **)&rules_string, &len);
391 	if (error != ENOENT) {
392 		if (error != 0)
393 			return (error);
394 		if (len > MAC_RULE_STRING_LEN) {
395 			vfs_opterror(opts, "mdo.rules too long");
396 			return (ENAMETOOLONG);
397 		}
398 	}
399 	if (error == ENOENT)
400 		error = 0;
401 	return (error);
402 }
403 
404 static int
405 mac_do_jail_set(void *obj, void *data)
406 {
407 	struct prison *pr = obj;
408 	struct vfsoptlist *opts = data;
409 	char *rules_string;
410 	int error, jsys, len;
411 
412 	error = vfs_copyopt(opts, "mdo", &jsys, sizeof(jsys));
413 	if (error == ENOENT)
414 		jsys = -1;
415 	error = vfs_getopt(opts, "mdo.rules", (void **)&rules_string, &len);
416 	if (error == ENOENT)
417 		rules_string = "";
418 	else
419 		jsys = JAIL_SYS_NEW;
420 	switch (jsys) {
421 	case JAIL_SYS_INHERIT:
422 		remove_rules(pr);
423 		error = 0;
424 		break;
425 	case JAIL_SYS_NEW:
426 		error = parse_and_set_rules(pr, rules_string);
427 		break;
428 	}
429 	return (error);
430 }
431 
432 /*
433  * OSD jail methods.
434  *
435  * There is no PR_METHOD_REMOVE, as OSD storage is destroyed by the common jail
436  * code (see prison_cleanup()), which triggers a run of our dealloc_osd()
437  * destructor.
438  */
439 static const osd_method_t osd_methods[PR_MAXMETHOD] = {
440 	[PR_METHOD_CREATE] = mac_do_jail_create,
441 	[PR_METHOD_GET] = mac_do_jail_get,
442 	[PR_METHOD_CHECK] = mac_do_jail_check,
443 	[PR_METHOD_SET] = mac_do_jail_set,
444 };
445 
446 
447 static void
448 init(struct mac_policy_conf *mpc)
449 {
450 	struct prison *pr;
451 
452 	mac_do_osd_jail_slot = osd_jail_register(dealloc_osd, osd_methods);
453 	set_empty_rules(&prison0);
454 	sx_slock(&allprison_lock);
455 	TAILQ_FOREACH(pr, &allprison, pr_list)
456 	    set_empty_rules(pr);
457 	sx_sunlock(&allprison_lock);
458 }
459 
460 static void
461 destroy(struct mac_policy_conf *mpc)
462 {
463 	osd_jail_deregister(mac_do_osd_jail_slot);
464 }
465 
466 static bool
467 rule_applies(struct ucred *cred, struct rule *r)
468 {
469 	if (r->from_type == RULE_UID && r->f_uid == cred->cr_uid)
470 		return (true);
471 	if (r->from_type == RULE_GID && groupmember(r->f_gid, cred))
472 		return (true);
473 	return (false);
474 }
475 
476 static int
477 priv_grant(struct ucred *cred, int priv)
478 {
479 	struct rule *r;
480 	struct prison *pr;
481 	struct rules *rule;
482 
483 	if (do_enabled == 0)
484 		return (EPERM);
485 
486 	rule = find_rules(cred->cr_prison, &pr);
487 	TAILQ_FOREACH(r, &rule->head, r_entries) {
488 		if (rule_applies(cred, r)) {
489 			switch (priv) {
490 			case PRIV_CRED_SETGROUPS:
491 			case PRIV_CRED_SETUID:
492 				prison_unlock(pr);
493 				return (0);
494 			default:
495 				break;
496 			}
497 		}
498 	}
499 	prison_unlock(pr);
500 	return (EPERM);
501 }
502 
503 static int
504 check_setgroups(struct ucred *cred, int ngrp, gid_t *groups)
505 {
506 	struct rule *r;
507 	char *fullpath = NULL;
508 	char *freebuf = NULL;
509 	struct prison *pr;
510 	struct rules *rule;
511 
512 	if (do_enabled == 0)
513 		return (0);
514 	if (cred->cr_uid == 0)
515 		return (0);
516 
517 	if (vn_fullpath(curproc->p_textvp, &fullpath, &freebuf) != 0)
518 		return (EPERM);
519 	if (strcmp(fullpath, "/usr/bin/mdo") != 0) {
520 		free(freebuf, M_TEMP);
521 		return (EPERM);
522 	}
523 	free(freebuf, M_TEMP);
524 
525 	rule = find_rules(cred->cr_prison, &pr);
526 	TAILQ_FOREACH(r, &rule->head, r_entries) {
527 		if (rule_applies(cred, r)) {
528 			prison_unlock(pr);
529 			return (0);
530 		}
531 	}
532 	prison_unlock(pr);
533 
534 	return (EPERM);
535 }
536 
537 static int
538 check_setuid(struct ucred *cred, uid_t uid)
539 {
540 	struct rule *r;
541 	int error;
542 	char *fullpath = NULL;
543 	char *freebuf = NULL;
544 	struct prison *pr;
545 	struct rules *rule;
546 
547 	if (do_enabled == 0)
548 		return (0);
549 	if (cred->cr_uid == uid || cred->cr_uid == 0 || cred->cr_ruid == 0)
550 		return (0);
551 
552 	if (vn_fullpath(curproc->p_textvp, &fullpath, &freebuf) != 0)
553 		return (EPERM);
554 	if (strcmp(fullpath, "/usr/bin/mdo") != 0) {
555 		free(freebuf, M_TEMP);
556 		return (EPERM);
557 	}
558 	free(freebuf, M_TEMP);
559 
560 	error = EPERM;
561 	rule = find_rules(cred->cr_prison, &pr);
562 	TAILQ_FOREACH(r, &rule->head, r_entries) {
563 		if (r->from_type == RULE_UID) {
564 			if (cred->cr_uid != r->f_uid)
565 				continue;
566 			if (r->to_type == RULE_ANY) {
567 				error = 0;
568 				break;
569 			}
570 			if (r->to_type == RULE_UID && uid == r->t_uid) {
571 				error = 0;
572 				break;
573 			}
574 		}
575 		if (r->from_type == RULE_GID) {
576 			if (!groupmember(r->f_gid, cred))
577 				continue;
578 			if (r->to_type == RULE_ANY) {
579 				error = 0;
580 				break;
581 			}
582 			if (r->to_type == RULE_UID && uid == r->t_uid) {
583 				error = 0;
584 				break;
585 			}
586 		}
587 	}
588 	prison_unlock(pr);
589 	return (error);
590 }
591 
592 static struct mac_policy_ops do_ops = {
593 	.mpo_destroy = destroy,
594 	.mpo_init = init,
595 	.mpo_cred_check_setuid = check_setuid,
596 	.mpo_cred_check_setgroups = check_setgroups,
597 	.mpo_priv_grant = priv_grant,
598 };
599 
600 MAC_POLICY_SET(&do_ops, mac_do, "MAC/do",
601    MPC_LOADTIME_FLAG_UNLOADOK, NULL);
602 MODULE_VERSION(mac_do, 1);
603