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