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/ctype.h> 10 #include <sys/jail.h> 11 #include <sys/kernel.h> 12 #include <sys/limits.h> 13 #include <sys/lock.h> 14 #include <sys/malloc.h> 15 #include <sys/module.h> 16 #include <sys/mount.h> 17 #include <sys/mutex.h> 18 #include <sys/priv.h> 19 #include <sys/proc.h> 20 #include <sys/socket.h> 21 #include <sys/sx.h> 22 #include <sys/sysctl.h> 23 #include <sys/ucred.h> 24 #include <sys/vnode.h> 25 26 #include <security/mac/mac_policy.h> 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 /* 46 * We assume that 'uid_t' and 'gid_t' are aliases to 'u_int' in conversions 47 * required for parsing rules specification strings. 48 */ 49 _Static_assert(sizeof(uid_t) == sizeof(u_int) && (uid_t)-1 >= 0 && 50 sizeof(gid_t) == sizeof(u_int) && (gid_t)-1 >= 0, 51 "mac_do(4) assumes that 'uid_t' and 'gid_t' are aliases to 'u_int'"); 52 53 struct rule { 54 u_int from_type; 55 u_int from_id; 56 u_int to_type; 57 u_int to_id; 58 TAILQ_ENTRY(rule) r_entries; 59 }; 60 61 struct rules { 62 char string[MAC_RULE_STRING_LEN]; 63 TAILQ_HEAD(rulehead, rule) head; 64 }; 65 66 static void 67 toast_rules(struct rules *const rules) 68 { 69 struct rulehead *const head = &rules->head; 70 struct rule *rule; 71 72 while ((rule = TAILQ_FIRST(head)) != NULL) { 73 TAILQ_REMOVE(head, rule, r_entries); 74 free(rule, M_DO); 75 } 76 free(rules, M_DO); 77 } 78 79 static struct rules * 80 alloc_rules(void) 81 { 82 struct rules *const rules = malloc(sizeof(*rules), M_DO, M_WAITOK); 83 84 _Static_assert(MAC_RULE_STRING_LEN > 0, "MAC_RULE_STRING_LEN <= 0!"); 85 rules->string[0] = 0; 86 TAILQ_INIT(&rules->head); 87 return (rules); 88 } 89 90 /* 91 * String to unsigned int. 92 * 93 * Contrary to the "standard" strtou*() family of functions, do not tolerate 94 * spaces at start nor an empty string, and returns a status code, the 'u_int' 95 * result being returned through a passed pointer (if no error). 96 * 97 * We detour through 'quad_t' because in-kernel strto*() functions cannot set 98 * 'errno' and thus can't distinguish a true maximum value from one returned 99 * because of overflow. We use 'quad_t' instead of 'u_quad_t' to support 100 * negative specifications (e.g., such as "-1" for UINT_MAX). 101 */ 102 static int 103 strtoui_strict(const char *const restrict s, const char **const restrict endptr, 104 int base, u_int *result) 105 { 106 char *ep; 107 quad_t q; 108 109 /* Rule out spaces and empty specifications. */ 110 if (s[0] == '\0' || isspace(s[0])) { 111 if (endptr != NULL) 112 *endptr = s; 113 return (EINVAL); 114 } 115 116 q = strtoq(s, &ep, base); 117 if (endptr != NULL) 118 *endptr = ep; 119 if (q < 0) { 120 /* We allow specifying a negative number. */ 121 if (q < -(quad_t)UINT_MAX - 1 || q == QUAD_MIN) 122 return (EOVERFLOW); 123 } else { 124 if (q > UINT_MAX || q == UQUAD_MAX) 125 return (EOVERFLOW); 126 } 127 128 *result = (u_int)q; 129 return (0); 130 } 131 132 static int 133 parse_rule_element(char *element, struct rule **rule) 134 { 135 const char *from_type, *from_id, *to, *p; 136 struct rule *new; 137 138 new = malloc(sizeof(*new), M_DO, M_ZERO|M_WAITOK); 139 140 from_type = strsep(&element, "="); 141 if (from_type == NULL) 142 goto einval; 143 144 if (strcmp(from_type, "uid") == 0) 145 new->from_type = RULE_UID; 146 else if (strcmp(from_type, "gid") == 0) 147 new->from_type = RULE_GID; 148 else 149 goto einval; 150 151 from_id = strsep(&element, ":"); 152 if (from_id == NULL || *from_id == '\0') 153 goto einval; 154 155 error = strtoui_strict(from_id, &p, 10, &new->from_id); 156 if (error != 0 || *p != '\0') 157 goto einval; 158 159 to = element; 160 if (to == NULL || *to == '\0') 161 goto einval; 162 163 if (strcmp(to, "any") == 0 || strcmp(to, "*") == 0) 164 new->to_type = RULE_ANY; 165 else { 166 new->to_type = RULE_UID; 167 error = strtoui_strict(to, &p, 10, &new->to_id); 168 if (error != 0 || *p != '\0') 169 goto einval; 170 } 171 172 *rule = new; 173 return (0); 174 einval: 175 free(new, M_DO); 176 *rule = NULL; 177 return (EINVAL); 178 } 179 180 /* 181 * Parse rules specification and produce rule structures out of it. 182 * 183 * Returns 0 on success, with '*rulesp' made to point to a 'struct rule' 184 * representing the rules. On error, the returned value is non-zero and 185 * '*rulesp' is unchanged. If 'string' has length greater or equal to 186 * MAC_RULE_STRING_LEN, ENAMETOOLONG is returned. If it is not in the expected 187 * format (comma-separated list of clauses of the form "<type>=<val>:<target>", 188 * where <type> is "uid" or "gid", <val> an UID or GID (depending on <type>) and 189 * <target> is "*", "any" or some UID), EINVAL is returned. 190 */ 191 static int 192 parse_rules(const char *const string, struct rules **const rulesp) 193 { 194 const size_t len = strlen(string); 195 char *copy; 196 char *p; 197 char *element; 198 struct rules *rules; 199 struct rule *new; 200 int error = 0; 201 202 if (len >= MAC_RULE_STRING_LEN) 203 return (ENAMETOOLONG); 204 205 rules = alloc_rules(); 206 bcopy(string, rules->string, len + 1); 207 MPASS(rules->string[len] == '\0'); /* Catch some races. */ 208 209 copy = malloc(len + 1, M_DO, M_WAITOK); 210 bcopy(string, copy, len + 1); 211 MPASS(copy[len] == '\0'); /* Catch some races. */ 212 213 p = copy; 214 while ((element = strsep(&p, ",")) != NULL) { 215 if (element[0] == '\0') 216 continue; 217 error = parse_rule_element(element, &new); 218 if (error != 0) { 219 toast_rules(rules); 220 goto out; 221 } 222 TAILQ_INSERT_TAIL(&rules->head, new, r_entries); 223 } 224 225 *rulesp = rules; 226 out: 227 free(copy, M_DO); 228 return (error); 229 } 230 231 /* 232 * Find rules applicable to the passed prison. 233 * 234 * Returns the applicable rules (and never NULL). 'pr' must be unlocked. 235 * 'aprp' is set to the (ancestor) prison holding these, and it must be unlocked 236 * once the caller is done accessing the rules. '*aprp' is equal to 'pr' if and 237 * only if the current jail has its own set of rules. 238 */ 239 static struct rules * 240 find_rules(struct prison *const pr, struct prison **const aprp) 241 { 242 struct prison *cpr, *ppr; 243 struct rules *rules; 244 245 cpr = pr; 246 for (;;) { 247 prison_lock(cpr); 248 rules = osd_jail_get(cpr, mac_do_osd_jail_slot); 249 if (rules != NULL) 250 break; 251 prison_unlock(cpr); 252 253 ppr = cpr->pr_parent; 254 MPASS(ppr != NULL); /* prison0 always has rules. */ 255 cpr = ppr; 256 } 257 *aprp = cpr; 258 259 return (rules); 260 } 261 262 /* 263 * OSD destructor for slot 'mac_do_osd_jail_slot'. 264 * 265 * Called with 'value' not NULL. 266 */ 267 static void 268 dealloc_osd(void *const value) 269 { 270 struct rules *const rules = value; 271 272 toast_rules(rules); 273 } 274 275 /* 276 * Remove the rules specifically associated to a prison. 277 * 278 * In practice, this means that the rules become inherited (from the closest 279 * ascendant that has some). 280 * 281 * Destroys the 'mac_do_osd_jail_slot' slot of the passed jail. 282 */ 283 static void 284 remove_rules(struct prison *const pr) 285 { 286 prison_lock(pr); 287 /* This calls destructor dealloc_osd(). */ 288 osd_jail_del(pr, mac_do_osd_jail_slot); 289 prison_unlock(pr); 290 } 291 292 /* 293 * Assign already built rules to a jail. 294 */ 295 static void 296 set_rules(struct prison *const pr, struct rules *const rules) 297 { 298 struct rules *old_rules; 299 void **rsv; 300 301 rsv = osd_reserve(mac_do_osd_jail_slot); 302 303 prison_lock(pr); 304 old_rules = osd_jail_get(pr, mac_do_osd_jail_slot); 305 osd_jail_set_reserved(pr, mac_do_osd_jail_slot, rsv, rules); 306 prison_unlock(pr); 307 if (old_rules != NULL) 308 toast_rules(old_rules); 309 } 310 311 /* 312 * Assigns empty rules to a jail. 313 */ 314 static void 315 set_empty_rules(struct prison *const pr) 316 { 317 struct rules *const rules = alloc_rules(); 318 319 set_rules(pr, rules); 320 } 321 322 /* 323 * Parse a rules specification and assign them to a jail. 324 * 325 * Returns the same error code as parse_rules() (which see). 326 */ 327 static int 328 parse_and_set_rules(struct prison *const pr, const char *rules_string) 329 { 330 struct rules *rules; 331 int error; 332 333 error = parse_rules(rules_string, &rules); 334 if (error != 0) 335 return (error); 336 set_rules(pr, rules); 337 return (0); 338 } 339 340 static int 341 mac_do_sysctl_rules(SYSCTL_HANDLER_ARGS) 342 { 343 char *const buf = malloc(MAC_RULE_STRING_LEN, M_DO, M_WAITOK); 344 struct prison *const td_pr = req->td->td_ucred->cr_prison; 345 struct prison *pr; 346 struct rules *rules; 347 int error; 348 349 rules = find_rules(td_pr, &pr); 350 strlcpy(buf, rules->string, MAC_RULE_STRING_LEN); 351 prison_unlock(pr); 352 353 error = sysctl_handle_string(oidp, buf, MAC_RULE_STRING_LEN, req); 354 if (error != 0 || req->newptr == NULL) 355 goto out; 356 357 /* Set our prison's rules, not that of the jail we inherited from. */ 358 error = parse_and_set_rules(td_pr, buf); 359 out: 360 free(buf, M_DO); 361 return (error); 362 } 363 364 SYSCTL_PROC(_security_mac_do, OID_AUTO, rules, 365 CTLTYPE_STRING|CTLFLAG_RW|CTLFLAG_PRISON|CTLFLAG_MPSAFE, 366 0, 0, mac_do_sysctl_rules, "A", 367 "Rules"); 368 369 370 SYSCTL_JAIL_PARAM_SYS_SUBNODE(mac, do, CTLFLAG_RW, "Jail MAC/do parameters"); 371 SYSCTL_JAIL_PARAM_STRING(_mac_do, rules, CTLFLAG_RW, MAC_RULE_STRING_LEN, 372 "Jail MAC/do rules"); 373 374 375 static int 376 mac_do_jail_create(void *obj, void *data __unused) 377 { 378 struct prison *const pr = obj; 379 380 set_empty_rules(pr); 381 return (0); 382 } 383 384 static int 385 mac_do_jail_get(void *obj, void *data) 386 { 387 struct prison *ppr, *const pr = obj; 388 struct vfsoptlist *const opts = data; 389 struct rules *rules; 390 int jsys, error; 391 392 rules = find_rules(pr, &ppr); 393 394 jsys = pr == ppr ? 395 (TAILQ_EMPTY(&rules->head) ? JAIL_SYS_DISABLE : JAIL_SYS_NEW) : 396 JAIL_SYS_INHERIT; 397 error = vfs_setopt(opts, "mac.do", &jsys, sizeof(jsys)); 398 if (error != 0 && error != ENOENT) 399 goto done; 400 401 error = vfs_setopts(opts, "mac.do.rules", rules->string); 402 if (error != 0 && error != ENOENT) 403 goto done; 404 405 error = 0; 406 done: 407 prison_unlock(ppr); 408 return (error); 409 } 410 411 /* 412 * -1 is used as a sentinel in mac_do_jail_check() and mac_do_jail_set() below. 413 */ 414 _Static_assert(-1 != JAIL_SYS_DISABLE && -1 != JAIL_SYS_NEW && 415 -1 != JAIL_SYS_INHERIT, 416 "mac_do(4) uses -1 as a sentinel for uninitialized 'jsys'."); 417 418 /* 419 * We perform only cheap checks here, i.e., we do not really parse the rules 420 * specification string, if any. 421 */ 422 static int 423 mac_do_jail_check(void *obj, void *data) 424 { 425 struct vfsoptlist *opts = data; 426 char *rules_string; 427 int error, jsys, size; 428 429 error = vfs_copyopt(opts, "mac.do", &jsys, sizeof(jsys)); 430 if (error == ENOENT) 431 jsys = -1; 432 else { 433 if (error != 0) 434 return (error); 435 if (jsys != JAIL_SYS_DISABLE && jsys != JAIL_SYS_NEW && 436 jsys != JAIL_SYS_INHERIT) 437 return (EINVAL); 438 } 439 440 /* 441 * We use vfs_getopt() here instead of vfs_getopts() to get the length. 442 * We perform the additional checks done by the latter here, even if 443 * jail_set() calls vfs_getopts() itself later (they becoming 444 * inconsistent wouldn't cause any security problem). 445 */ 446 error = vfs_getopt(opts, "mac.do.rules", (void**)&rules_string, &size); 447 if (error == ENOENT) { 448 /* 449 * Default (in absence of "mac.do.rules") is to disable (and, in 450 * particular, not inherit). 451 */ 452 if (jsys == -1) 453 jsys = JAIL_SYS_DISABLE; 454 455 if (jsys == JAIL_SYS_NEW) { 456 vfs_opterror(opts, "'mac.do.rules' must be specified " 457 "given 'mac.do''s value"); 458 return (EINVAL); 459 } 460 461 /* Absence of "mac.do.rules" at this point is OK. */ 462 error = 0; 463 } else { 464 if (error != 0) 465 return (error); 466 467 /* Not a proper string. */ 468 if (size == 0 || rules_string[size - 1] != '\0') { 469 vfs_opterror(opts, "'mac.do.rules' not a proper string"); 470 return (EINVAL); 471 } 472 473 if (size > MAC_RULE_STRING_LEN) { 474 vfs_opterror(opts, "'mdo.rules' too long"); 475 return (ENAMETOOLONG); 476 } 477 478 if (jsys == -1) 479 /* Default (if "mac.do.rules" is present). */ 480 jsys = rules_string[0] == '\0' ? JAIL_SYS_DISABLE : 481 JAIL_SYS_NEW; 482 483 /* 484 * Be liberal and accept JAIL_SYS_DISABLE and JAIL_SYS_INHERIT 485 * with an explicit empty rules specification. 486 */ 487 switch (jsys) { 488 case JAIL_SYS_DISABLE: 489 case JAIL_SYS_INHERIT: 490 if (rules_string[0] != '\0') { 491 vfs_opterror(opts, "'mac.do.rules' specified " 492 "but should not given 'mac.do''s value"); 493 return (EINVAL); 494 } 495 break; 496 } 497 } 498 499 return (error); 500 } 501 502 static int 503 mac_do_jail_set(void *obj, void *data) 504 { 505 struct prison *pr = obj; 506 struct vfsoptlist *opts = data; 507 char *rules_string; 508 int error, jsys; 509 510 /* 511 * The invariants checks used below correspond to what has already been 512 * checked in jail_check() above. 513 */ 514 515 error = vfs_copyopt(opts, "mac.do", &jsys, sizeof(jsys)); 516 MPASS(error == 0 || error == ENOENT); 517 if (error != 0) 518 jsys = -1; /* Mark unfilled. */ 519 520 rules_string = vfs_getopts(opts, "mac.do.rules", &error); 521 MPASS(error == 0 || error == ENOENT); 522 if (error == 0) { 523 MPASS(strlen(rules_string) < MAC_RULE_STRING_LEN); 524 if (jsys == -1) 525 /* Default (if "mac.do.rules" is present). */ 526 jsys = rules_string[0] == '\0' ? JAIL_SYS_DISABLE : 527 JAIL_SYS_NEW; 528 else 529 MPASS(jsys == JAIL_SYS_NEW || 530 ((jsys == JAIL_SYS_DISABLE || 531 jsys == JAIL_SYS_INHERIT) && 532 rules_string[0] == '\0')); 533 } else { 534 MPASS(jsys != JAIL_SYS_NEW); 535 if (jsys == -1) 536 /* 537 * Default (in absence of "mac.do.rules") is to disable 538 * (and, in particular, not inherit). 539 */ 540 jsys = JAIL_SYS_DISABLE; 541 /* If disabled, we'll store an empty rule specification. */ 542 if (jsys == JAIL_SYS_DISABLE) 543 rules_string = ""; 544 } 545 546 switch (jsys) { 547 case JAIL_SYS_INHERIT: 548 remove_rules(pr); 549 error = 0; 550 break; 551 case JAIL_SYS_DISABLE: 552 case JAIL_SYS_NEW: 553 error = parse_and_set_rules(pr, rules_string); 554 break; 555 default: 556 __assert_unreachable(); 557 } 558 return (error); 559 } 560 561 /* 562 * OSD jail methods. 563 * 564 * There is no PR_METHOD_REMOVE, as OSD storage is destroyed by the common jail 565 * code (see prison_cleanup()), which triggers a run of our dealloc_osd() 566 * destructor. 567 */ 568 static const osd_method_t osd_methods[PR_MAXMETHOD] = { 569 [PR_METHOD_CREATE] = mac_do_jail_create, 570 [PR_METHOD_GET] = mac_do_jail_get, 571 [PR_METHOD_CHECK] = mac_do_jail_check, 572 [PR_METHOD_SET] = mac_do_jail_set, 573 }; 574 575 576 static void 577 mac_do_init(struct mac_policy_conf *mpc) 578 { 579 struct prison *pr; 580 581 mac_do_osd_jail_slot = osd_jail_register(dealloc_osd, osd_methods); 582 set_empty_rules(&prison0); 583 sx_slock(&allprison_lock); 584 TAILQ_FOREACH(pr, &allprison, pr_list) 585 set_empty_rules(pr); 586 sx_sunlock(&allprison_lock); 587 } 588 589 static void 590 mac_do_destroy(struct mac_policy_conf *mpc) 591 { 592 osd_jail_deregister(mac_do_osd_jail_slot); 593 } 594 595 static bool 596 rule_applies(struct ucred *cred, struct rule *r) 597 { 598 if (r->from_type == RULE_UID && r->from_id == cred->cr_uid) 599 return (true); 600 if (r->from_type == RULE_GID && groupmember(r->from_id, cred)) 601 return (true); 602 return (false); 603 } 604 605 static int 606 mac_do_priv_grant(struct ucred *cred, int priv) 607 { 608 struct rule *r; 609 struct prison *pr; 610 struct rules *rule; 611 612 if (do_enabled == 0) 613 return (EPERM); 614 615 rule = find_rules(cred->cr_prison, &pr); 616 TAILQ_FOREACH(r, &rule->head, r_entries) { 617 if (rule_applies(cred, r)) { 618 switch (priv) { 619 case PRIV_CRED_SETGROUPS: 620 case PRIV_CRED_SETUID: 621 prison_unlock(pr); 622 return (0); 623 default: 624 break; 625 } 626 } 627 } 628 prison_unlock(pr); 629 return (EPERM); 630 } 631 632 static int 633 mac_do_check_setgroups(struct ucred *cred, int ngrp, gid_t *groups) 634 { 635 struct rule *r; 636 char *fullpath = NULL; 637 char *freebuf = NULL; 638 struct prison *pr; 639 struct rules *rule; 640 641 if (do_enabled == 0) 642 return (0); 643 if (cred->cr_uid == 0) 644 return (0); 645 646 if (vn_fullpath(curproc->p_textvp, &fullpath, &freebuf) != 0) 647 return (EPERM); 648 if (strcmp(fullpath, "/usr/bin/mdo") != 0) { 649 free(freebuf, M_TEMP); 650 return (EPERM); 651 } 652 free(freebuf, M_TEMP); 653 654 rule = find_rules(cred->cr_prison, &pr); 655 TAILQ_FOREACH(r, &rule->head, r_entries) { 656 if (rule_applies(cred, r)) { 657 prison_unlock(pr); 658 return (0); 659 } 660 } 661 prison_unlock(pr); 662 663 return (EPERM); 664 } 665 666 static int 667 mac_do_check_setuid(struct ucred *cred, uid_t uid) 668 { 669 struct rule *r; 670 int error; 671 char *fullpath = NULL; 672 char *freebuf = NULL; 673 struct prison *pr; 674 struct rules *rule; 675 676 if (do_enabled == 0) 677 return (0); 678 if (cred->cr_uid == uid || cred->cr_uid == 0 || cred->cr_ruid == 0) 679 return (0); 680 681 if (vn_fullpath(curproc->p_textvp, &fullpath, &freebuf) != 0) 682 return (EPERM); 683 if (strcmp(fullpath, "/usr/bin/mdo") != 0) { 684 free(freebuf, M_TEMP); 685 return (EPERM); 686 } 687 free(freebuf, M_TEMP); 688 689 error = EPERM; 690 rule = find_rules(cred->cr_prison, &pr); 691 TAILQ_FOREACH(r, &rule->head, r_entries) { 692 if (r->from_type == RULE_UID) { 693 if (cred->cr_uid != r->from_id) 694 continue; 695 if (r->to_type == RULE_ANY) { 696 error = 0; 697 break; 698 } 699 if (r->to_type == RULE_UID && uid == r->to_id) { 700 error = 0; 701 break; 702 } 703 } 704 if (r->from_type == RULE_GID) { 705 if (!groupmember(r->from_id, cred)) 706 continue; 707 if (r->to_type == RULE_ANY) { 708 error = 0; 709 break; 710 } 711 if (r->to_type == RULE_UID && uid == r->to_id) { 712 error = 0; 713 break; 714 } 715 } 716 } 717 prison_unlock(pr); 718 return (error); 719 } 720 721 static struct mac_policy_ops do_ops = { 722 .mpo_destroy = mac_do_destroy, 723 .mpo_init = mac_do_init, 724 .mpo_cred_check_setuid = mac_do_check_setuid, 725 .mpo_cred_check_setgroups = mac_do_check_setgroups, 726 .mpo_priv_grant = mac_do_priv_grant, 727 }; 728 729 MAC_POLICY_SET(&do_ops, mac_do, "MAC/do", 730 MPC_LOADTIME_FLAG_UNLOADOK, NULL); 731 MODULE_VERSION(mac_do, 1); 732