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