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