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