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