1 /*-
2 * Copyright(c) 2024 Baptiste Daroussin <[email protected]>
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
toast_rules(struct rulehead * head)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
parse_rule_element(char * element,struct rule ** rule)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
parse_rules(char * string,struct rulehead * head)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 *
mac_do_rule_find(struct prison * spr,struct prison ** prp)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
sysctl_rules(SYSCTL_HANDLER_ARGS)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
destroy(struct mac_policy_conf * mpc)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
mac_do_alloc_prison(struct prison * pr,struct mac_do_rule ** lrp)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
mac_do_dealloc_prison(void * data)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
mac_do_prison_set(void * obj,void * data)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
mac_do_prison_get(void * obj,void * data)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
mac_do_prison_create(void * obj,void * data __unused)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
mac_do_prison_remove(void * obj,void * data __unused)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
mac_do_prison_check(void * obj,void * data)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
init(struct mac_policy_conf * mpc)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
rule_is_valid(struct ucred * cred,struct rule * r)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 && r->f_gid == cred->cr_gid)
415 return (true);
416 return (false);
417 }
418
419 static int
priv_grant(struct ucred * cred,int priv)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
check_setgroups(struct ucred * cred,int ngrp,gid_t * groups)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
check_setuid(struct ucred * cred,uid_t uid)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 (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
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