xref: /freebsd/sys/security/mac_do/mac_do.c (revision 7937bfbc0ca53fe7cdd0d54414f9296e273a518e)
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/malloc.h>
9 #include <sys/jail.h>
10 #include <sys/kernel.h>
11 #include <sys/lock.h>
12 #include <sys/module.h>
13 #include <sys/mount.h>
14 #include <sys/mutex.h>
15 #include <sys/priv.h>
16 #include <sys/proc.h>
17 #include <sys/socket.h>
18 #include <sys/sx.h>
19 #include <sys/sysctl.h>
20 #include <sys/systm.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 mac_do_rule {
57 	char string[MAC_RULE_STRING_LEN];
58 	TAILQ_HEAD(rulehead, rule) head;
59 };
60 
61 static struct mac_do_rule rules0;
62 
63 static void
64 toast_rules(struct rulehead *head)
65 {
66 	struct rule *r;
67 
68 	while ((r = TAILQ_FIRST(head)) != NULL) {
69 		TAILQ_REMOVE(head, r, r_entries);
70 		free(r, M_DO);
71 	}
72 }
73 
74 static int
75 parse_rule_element(char *element, struct rule **rule)
76 {
77 	int error = 0;
78 	char *type, *id, *p;
79 	struct rule *new;
80 
81 	new = malloc(sizeof(*new), M_DO, M_ZERO|M_WAITOK);
82 
83 	type = strsep(&element, "=");
84 	if (type == NULL) {
85 		error = EINVAL;
86 		goto out;
87 	}
88 	if (strcmp(type, "uid") == 0) {
89 		new->from_type = RULE_UID;
90 	} else if (strcmp(type, "gid") == 0) {
91 		new->from_type = RULE_GID;
92 	} else {
93 		error = EINVAL;
94 		goto out;
95 	}
96 	id = strsep(&element, ":");
97 	if (id == NULL) {
98 		error = EINVAL;
99 		goto out;
100 	}
101 	if (new->from_type == RULE_UID)
102 		new->f_uid = strtol(id, &p, 10);
103 	if (new->from_type == RULE_GID)
104 		new->f_gid = strtol(id, &p, 10);
105 	if (*p != '\0') {
106 		error = EINVAL;
107 		goto out;
108 	}
109 	if (*element == '\0') {
110 		error = EINVAL;
111 		goto out;
112 	}
113 	if (strcmp(element, "any") == 0 || strcmp(element, "*") == 0) {
114 		new->to_type = RULE_ANY;
115 	} else {
116 		new->to_type = RULE_UID;
117 		new->t_uid = strtol(element, &p, 10);
118 		if (*p != '\0') {
119 			error = EINVAL;
120 			goto out;
121 		}
122 	}
123 out:
124 	if (error != 0) {
125 		free(new, M_DO);
126 		*rule = NULL;
127 	} else
128 		*rule = new;
129 	return (error);
130 }
131 
132 static int
133 parse_rules(char *string, struct rulehead *head)
134 {
135 	struct rule *new;
136 	char *element;
137 	int error = 0;
138 
139 	while ((element = strsep(&string, ",")) != NULL) {
140 		if (strlen(element) == 0)
141 			continue;
142 		error = parse_rule_element(element, &new);
143 		if (error)
144 			goto out;
145 		TAILQ_INSERT_TAIL(head, new, r_entries);
146 	}
147 out:
148 	if (error != 0)
149 		toast_rules(head);
150 	return (error);
151 }
152 
153 static struct mac_do_rule *
154 mac_do_rule_find(struct prison *spr, struct prison **prp)
155 {
156 	struct prison *pr;
157 	struct mac_do_rule *rules;
158 
159 	for (pr = spr;; pr = pr->pr_parent) {
160 		mtx_lock(&pr->pr_mtx);
161 		if (pr == &prison0) {
162 			rules = &rules0;
163 			break;
164 		}
165 		rules = osd_jail_get(pr, mac_do_osd_jail_slot);
166 		if (rules != NULL)
167 			break;
168 		mtx_unlock(&pr->pr_mtx);
169 	}
170 	*prp = pr;
171 
172 	return (rules);
173 }
174 
175 static int
176 sysctl_rules(SYSCTL_HANDLER_ARGS)
177 {
178 	char *copy_string, *new_string;
179 	struct rulehead head, saved_head;
180 	struct prison *pr;
181 	struct mac_do_rule *rules;
182 	int error;
183 
184 	rules = mac_do_rule_find(req->td->td_ucred->cr_prison, &pr);
185 	mtx_unlock(&pr->pr_mtx);
186 	if (req->newptr == NULL)
187 		return (sysctl_handle_string(oidp, rules->string, MAC_RULE_STRING_LEN, req));
188 
189 	new_string = malloc(MAC_RULE_STRING_LEN, M_DO,
190 	    M_WAITOK|M_ZERO);
191 	mtx_lock(&pr->pr_mtx);
192 	strlcpy(new_string, rules->string, MAC_RULE_STRING_LEN);
193 	mtx_unlock(&pr->pr_mtx);
194 
195 	error = sysctl_handle_string(oidp, new_string, MAC_RULE_STRING_LEN, req);
196 	if (error)
197 		goto out;
198 
199 	copy_string = strdup(new_string, M_DO);
200 	TAILQ_INIT(&head);
201 	error = parse_rules(copy_string, &head);
202 	free(copy_string, M_DO);
203 	if (error)
204 		goto out;
205 	TAILQ_INIT(&saved_head);
206 	mtx_lock(&pr->pr_mtx);
207 	TAILQ_CONCAT(&saved_head, &rules->head, r_entries);
208 	TAILQ_CONCAT(&rules->head, &head, r_entries);
209 	strlcpy(rules->string, new_string, MAC_RULE_STRING_LEN);
210 	mtx_unlock(&pr->pr_mtx);
211 	toast_rules(&saved_head);
212 
213 out:
214 	free(new_string, M_DO);
215 	return (error);
216 }
217 
218 SYSCTL_PROC(_security_mac_do, OID_AUTO, rules,
219     CTLTYPE_STRING|CTLFLAG_RW|CTLFLAG_MPSAFE,
220     0, 0, sysctl_rules, "A",
221     "Rules");
222 
223 static void
224 destroy(struct mac_policy_conf *mpc)
225 {
226 	osd_jail_deregister(mac_do_osd_jail_slot);
227 	toast_rules(&rules0.head);
228 }
229 
230 static void
231 mac_do_alloc_prison(struct prison *pr, struct mac_do_rule **lrp)
232 {
233 	struct prison *ppr;
234 	struct mac_do_rule *rules, *new_rules;
235 	void **rsv;
236 
237 	rules = mac_do_rule_find(pr, &ppr);
238 	if (ppr == pr)
239 		goto done;
240 
241 	mtx_unlock(&ppr->pr_mtx);
242 	new_rules = malloc(sizeof(*new_rules), M_PRISON, M_WAITOK|M_ZERO);
243 	rsv = osd_reserve(mac_do_osd_jail_slot);
244 	rules = mac_do_rule_find(pr, &ppr);
245 	if (ppr == pr) {
246 		free(new_rules, M_PRISON);
247 		osd_free_reserved(rsv);
248 		goto done;
249 	}
250 	mtx_lock(&pr->pr_mtx);
251 	osd_jail_set_reserved(pr, mac_do_osd_jail_slot, rsv, new_rules);
252 	TAILQ_INIT(&new_rules->head);
253 done:
254 	if (lrp != NULL)
255 		*lrp = rules;
256 	mtx_unlock(&pr->pr_mtx);
257 	mtx_unlock(&ppr->pr_mtx);
258 }
259 
260 static void
261 mac_do_dealloc_prison(void *data)
262 {
263 	struct mac_do_rule *r = data;
264 
265 	toast_rules(&r->head);
266 }
267 
268 static int
269 mac_do_prison_set(void *obj, void *data)
270 {
271 	struct prison *pr = obj;
272 	struct vfsoptlist *opts = data;
273 	struct rulehead head, saved_head;
274 	struct mac_do_rule *rules;
275 	char *rules_string, *copy_string;
276 	int error, jsys, len;
277 
278 	error = vfs_copyopt(opts, "mdo", &jsys, sizeof(jsys));
279 	if (error == ENOENT)
280 		jsys = -1;
281 	error = vfs_getopt(opts, "mdo.rules", (void **)&rules_string, &len);
282 	if (error == ENOENT)
283 		rules = NULL;
284 	else
285 		jsys = JAIL_SYS_NEW;
286 	switch (jsys) {
287 	case JAIL_SYS_INHERIT:
288 		mtx_lock(&pr->pr_mtx);
289 		osd_jail_del(pr, mac_do_osd_jail_slot);
290 		mtx_unlock(&pr->pr_mtx);
291 		break;
292 	case JAIL_SYS_NEW:
293 		mac_do_alloc_prison(pr, &rules);
294 		if (rules_string == NULL)
295 			break;
296 		copy_string = strdup(rules_string, M_DO);
297 		TAILQ_INIT(&head);
298 		error = parse_rules(copy_string, &head);
299 		free(copy_string, M_DO);
300 		if (error)
301 			return (1);
302 		TAILQ_INIT(&saved_head);
303 		mtx_lock(&pr->pr_mtx);
304 		TAILQ_CONCAT(&saved_head, &rules->head, r_entries);
305 		TAILQ_CONCAT(&rules->head, &head, r_entries);
306 		strlcpy(rules->string, rules_string, MAC_RULE_STRING_LEN);
307 		mtx_unlock(&pr->pr_mtx);
308 		toast_rules(&saved_head);
309 		break;
310 	}
311 	return (0);
312 }
313 
314 SYSCTL_JAIL_PARAM_SYS_NODE(mdo, CTLFLAG_RW, "Jail MAC/do parameters");
315 SYSCTL_JAIL_PARAM_STRING(_mdo, rules, CTLFLAG_RW, MAC_RULE_STRING_LEN,
316     "Jail MAC/do rules");
317 
318 static int
319 mac_do_prison_get(void *obj, void *data)
320 {
321 	struct prison *ppr, *pr = obj;
322 	struct vfsoptlist *opts = data;
323 	struct mac_do_rule *rules;
324 	int jsys, error;
325 
326 	rules = mac_do_rule_find(pr, &ppr);
327 	error = vfs_setopt(opts, "mdo", &jsys, sizeof(jsys));
328 	if (error != 0 && error != ENOENT)
329 		goto done;
330 	error = vfs_setopts(opts, "mdo.rules", rules->string);
331 	if (error != 0 && error != ENOENT)
332 		goto done;
333 	mtx_unlock(&ppr->pr_mtx);
334 	error = 0;
335 done:
336 	return (0);
337 }
338 
339 static int
340 mac_do_prison_create(void *obj, void *data __unused)
341 {
342 	struct prison *pr = obj;
343 
344 	mac_do_alloc_prison(pr, NULL);
345 	return (0);
346 }
347 
348 static int
349 mac_do_prison_remove(void *obj, void *data __unused)
350 {
351 	struct prison *pr = obj;
352 	struct mac_do_rule *r;
353 
354 	mtx_lock(&pr->pr_mtx);
355 	r = osd_jail_get(pr, mac_do_osd_jail_slot);
356 	mtx_unlock(&pr->pr_mtx);
357 	toast_rules(&r->head);
358 	return (0);
359 }
360 
361 static int
362 mac_do_prison_check(void *obj, void *data)
363 {
364 	struct vfsoptlist *opts = data;
365 	char *rules_string;
366 	int error, jsys, len;
367 
368 	error = vfs_copyopt(opts, "mdo", &jsys, sizeof(jsys));
369 	if (error != ENOENT) {
370 		if (error != 0)
371 			return (error);
372 		if (jsys != JAIL_SYS_NEW && jsys != JAIL_SYS_INHERIT)
373 			return (EINVAL);
374 	}
375 	error = vfs_getopt(opts, "mdo.rules", (void **)&rules_string, &len);
376 	if (error != ENOENT) {
377 		if (error != 0)
378 			return (error);
379 		if (len > MAC_RULE_STRING_LEN) {
380 			vfs_opterror(opts, "mdo.rules too long");
381 			return (ENAMETOOLONG);
382 		}
383 	}
384 	if (error == ENOENT)
385 		error = 0;
386 	return (error);
387 }
388 
389 static void
390 init(struct mac_policy_conf *mpc)
391 {
392 	static osd_method_t methods[PR_MAXMETHOD] = {
393 		[PR_METHOD_CREATE] = mac_do_prison_create,
394 		[PR_METHOD_GET] = mac_do_prison_get,
395 		[PR_METHOD_SET] = mac_do_prison_set,
396 		[PR_METHOD_CHECK] = mac_do_prison_check,
397 		[PR_METHOD_REMOVE] = mac_do_prison_remove,
398 	};
399 	struct prison *pr;
400 
401 	mac_do_osd_jail_slot = osd_jail_register(mac_do_dealloc_prison, methods);
402 	TAILQ_INIT(&rules0.head);
403 	sx_slock(&allprison_lock);
404 	TAILQ_FOREACH(pr, &allprison, pr_list)
405 		mac_do_alloc_prison(pr, NULL);
406 	sx_sunlock(&allprison_lock);
407 }
408 
409 static bool
410 rule_is_valid(struct ucred *cred, struct rule *r)
411 {
412 	if (r->from_type == RULE_UID && r->f_uid == cred->cr_uid)
413 		return (true);
414 	if (r->from_type == RULE_GID && groupmember(r->f_gid, cred))
415 		return (true);
416 	return (false);
417 }
418 
419 static int
420 priv_grant(struct ucred *cred, int priv)
421 {
422 	struct rule *r;
423 	struct prison *pr;
424 	struct mac_do_rule *rule;
425 
426 	if (do_enabled == 0)
427 		return (EPERM);
428 
429 	rule = mac_do_rule_find(cred->cr_prison, &pr);
430 	TAILQ_FOREACH(r, &rule->head, r_entries) {
431 		if (rule_is_valid(cred, r)) {
432 			switch (priv) {
433 			case PRIV_CRED_SETGROUPS:
434 			case PRIV_CRED_SETUID:
435 				mtx_unlock(&pr->pr_mtx);
436 				return (0);
437 			default:
438 				break;
439 			}
440 		}
441 	}
442 	mtx_unlock(&pr->pr_mtx);
443 	return (EPERM);
444 }
445 
446 static int
447 check_setgroups(struct ucred *cred, int ngrp, gid_t *groups)
448 {
449 	struct rule *r;
450 	char *fullpath = NULL;
451 	char *freebuf = NULL;
452 	struct prison *pr;
453 	struct mac_do_rule *rule;
454 
455 	if (do_enabled == 0)
456 		return (0);
457 	if (cred->cr_uid == 0)
458 		return (0);
459 
460 	if (vn_fullpath(curproc->p_textvp, &fullpath, &freebuf) != 0)
461 		return (EPERM);
462 	if (strcmp(fullpath, "/usr/bin/mdo") != 0) {
463 		free(freebuf, M_TEMP);
464 		return (EPERM);
465 	}
466 	free(freebuf, M_TEMP);
467 
468 	rule = mac_do_rule_find(cred->cr_prison, &pr);
469 	TAILQ_FOREACH(r, &rule->head, r_entries) {
470 		if (rule_is_valid(cred, r)) {
471 			mtx_unlock(&pr->pr_mtx);
472 			return (0);
473 		}
474 	}
475 	mtx_unlock(&pr->pr_mtx);
476 
477 	return (EPERM);
478 }
479 
480 static int
481 check_setuid(struct ucred *cred, uid_t uid)
482 {
483 	struct rule *r;
484 	int error;
485 	char *fullpath = NULL;
486 	char *freebuf = NULL;
487 	struct prison *pr;
488 	struct mac_do_rule *rule;
489 
490 	if (do_enabled == 0)
491 		return (0);
492 	if (cred->cr_uid == uid || cred->cr_uid == 0 || cred->cr_ruid == 0)
493 		return (0);
494 
495 	if (vn_fullpath(curproc->p_textvp, &fullpath, &freebuf) != 0)
496 		return (EPERM);
497 	if (strcmp(fullpath, "/usr/bin/mdo") != 0) {
498 		free(freebuf, M_TEMP);
499 		return (EPERM);
500 	}
501 	free(freebuf, M_TEMP);
502 
503 	error = EPERM;
504 	rule = mac_do_rule_find(cred->cr_prison, &pr);
505 	TAILQ_FOREACH(r, &rule->head, r_entries) {
506 		if (r->from_type == RULE_UID) {
507 			if (cred->cr_uid != r->f_uid)
508 				continue;
509 			if (r->to_type == RULE_ANY) {
510 				error = 0;
511 				break;
512 			}
513 			if (r->to_type == RULE_UID && uid == r->t_uid) {
514 				error = 0;
515 				break;
516 			}
517 		}
518 		if (r->from_type == RULE_GID) {
519 			if (!groupmember(r->f_gid, cred))
520 				continue;
521 			if (r->to_type == RULE_ANY) {
522 				error = 0;
523 				break;
524 			}
525 			if (r->to_type == RULE_UID && uid == r->t_uid) {
526 				error = 0;
527 				break;
528 			}
529 		}
530 	}
531 	mtx_unlock(&pr->pr_mtx);
532 	return (error);
533 }
534 
535 static struct mac_policy_ops do_ops = {
536 	.mpo_destroy = destroy,
537 	.mpo_init = init,
538 	.mpo_cred_check_setuid = check_setuid,
539 	.mpo_cred_check_setgroups = check_setgroups,
540 	.mpo_priv_grant = priv_grant,
541 };
542 
543 MAC_POLICY_SET(&do_ops, mac_do, "MAC/do",
544    MPC_LOADTIME_FLAG_UNLOADOK, NULL);
545 MODULE_VERSION(mac_do, 1);
546