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
26SYSCTL_DECL(_security_mac);
27
28static SYSCTL_NODE(_security_mac, OID_AUTO, do,
29    CTLFLAG_RW|CTLFLAG_MPSAFE, 0, "mac_do policy controls");
30
31static int	do_enabled = 1;
32SYSCTL_INT(_security_mac_do, OID_AUTO, enabled, CTLFLAG_RWTUN,
33    &do_enabled, 0, "Enforce do policy");
34
35static MALLOC_DEFINE(M_DO, "do_rule", "Rules for mac_do");
36
37#define MAC_RULE_STRING_LEN	1024
38
39static unsigned		mac_do_osd_jail_slot;
40
41#define RULE_UID	1
42#define RULE_GID	2
43#define RULE_ANY	3
44
45struct 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
56struct mac_do_rule {
57	char string[MAC_RULE_STRING_LEN];
58	TAILQ_HEAD(rulehead, rule) head;
59};
60
61static struct mac_do_rule rules0;
62
63static void
64toast_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
74static int
75parse_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	}
123out:
124	if (error != 0) {
125		free(new, M_DO);
126		*rule = NULL;
127	} else
128		*rule = new;
129	return (error);
130}
131
132static int
133parse_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	}
147out:
148	if (error != 0)
149		toast_rules(head);
150	return (error);
151}
152
153static struct mac_do_rule *
154mac_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
175static int
176sysctl_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
213out:
214	free(new_string, M_DO);
215	return (error);
216}
217
218SYSCTL_PROC(_security_mac_do, OID_AUTO, rules,
219    CTLTYPE_STRING|CTLFLAG_RW|CTLFLAG_MPSAFE,
220    0, 0, sysctl_rules, "A",
221    "Rules");
222
223static void
224destroy(struct mac_policy_conf *mpc)
225{
226	osd_jail_deregister(mac_do_osd_jail_slot);
227	toast_rules(&rules0.head);
228}
229
230static void
231mac_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);
253done:
254	if (lrp != NULL)
255		*lrp = rules;
256	mtx_unlock(&pr->pr_mtx);
257	mtx_unlock(&ppr->pr_mtx);
258}
259
260static void
261mac_do_dealloc_prison(void *data)
262{
263	struct mac_do_rule *r = data;
264
265	toast_rules(&r->head);
266}
267
268static int
269mac_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
314SYSCTL_JAIL_PARAM_SYS_NODE(mdo, CTLFLAG_RW, "Jail MAC/do parameters");
315SYSCTL_JAIL_PARAM_STRING(_mdo, rules, CTLFLAG_RW, MAC_RULE_STRING_LEN,
316    "Jail MAC/do rules");
317
318static int
319mac_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;
335done:
336	return (0);
337}
338
339static int
340mac_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
348static int
349mac_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
361static int
362mac_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
389static void
390init(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
409static bool
410rule_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 && r->f_gid == cred->cr_gid)
415		return (true);
416	return (false);
417}
418
419static int
420priv_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
446static int
447check_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
480static int
481check_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 (cred->cr_gid != r->f_gid)
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
535static 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
543MAC_POLICY_SET(&do_ops, mac_do, "MAC/do",
544   MPC_LOADTIME_FLAG_UNLOADOK, NULL);
545MODULE_VERSION(mac_do, 1);
546