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