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