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 static int 381 mac_do_jail_check(void *obj, void *data) 382 { 383 struct vfsoptlist *opts = data; 384 char *rules_string; 385 int error, jsys, len; 386 387 error = vfs_copyopt(opts, "mac.do", &jsys, sizeof(jsys)); 388 if (error != ENOENT) { 389 if (error != 0) 390 return (error); 391 if (jsys != JAIL_SYS_NEW && jsys != JAIL_SYS_INHERIT) 392 return (EINVAL); 393 } 394 error = vfs_getopt(opts, "mac.do.rules", (void **)&rules_string, &len); 395 if (error != ENOENT) { 396 if (error != 0) 397 return (error); 398 if (len > MAC_RULE_STRING_LEN) { 399 vfs_opterror(opts, "mdo.rules too long"); 400 return (ENAMETOOLONG); 401 } 402 } 403 if (error == ENOENT) 404 error = 0; 405 return (error); 406 } 407 408 static int 409 mac_do_jail_set(void *obj, void *data) 410 { 411 struct prison *pr = obj; 412 struct vfsoptlist *opts = data; 413 char *rules_string; 414 int error, jsys, len; 415 416 error = vfs_copyopt(opts, "mac.do", &jsys, sizeof(jsys)); 417 if (error == ENOENT) 418 jsys = -1; 419 error = vfs_getopt(opts, "mac.do.rules", (void **)&rules_string, &len); 420 if (error == ENOENT) 421 rules_string = ""; 422 else 423 jsys = JAIL_SYS_NEW; 424 switch (jsys) { 425 case JAIL_SYS_INHERIT: 426 remove_rules(pr); 427 error = 0; 428 break; 429 case JAIL_SYS_NEW: 430 error = parse_and_set_rules(pr, rules_string); 431 break; 432 } 433 return (error); 434 } 435 436 /* 437 * OSD jail methods. 438 * 439 * There is no PR_METHOD_REMOVE, as OSD storage is destroyed by the common jail 440 * code (see prison_cleanup()), which triggers a run of our dealloc_osd() 441 * destructor. 442 */ 443 static const osd_method_t osd_methods[PR_MAXMETHOD] = { 444 [PR_METHOD_CREATE] = mac_do_jail_create, 445 [PR_METHOD_GET] = mac_do_jail_get, 446 [PR_METHOD_CHECK] = mac_do_jail_check, 447 [PR_METHOD_SET] = mac_do_jail_set, 448 }; 449 450 451 static void 452 mac_do_init(struct mac_policy_conf *mpc) 453 { 454 struct prison *pr; 455 456 mac_do_osd_jail_slot = osd_jail_register(dealloc_osd, osd_methods); 457 set_empty_rules(&prison0); 458 sx_slock(&allprison_lock); 459 TAILQ_FOREACH(pr, &allprison, pr_list) 460 set_empty_rules(pr); 461 sx_sunlock(&allprison_lock); 462 } 463 464 static void 465 mac_do_destroy(struct mac_policy_conf *mpc) 466 { 467 osd_jail_deregister(mac_do_osd_jail_slot); 468 } 469 470 static bool 471 rule_applies(struct ucred *cred, struct rule *r) 472 { 473 if (r->from_type == RULE_UID && r->f_uid == cred->cr_uid) 474 return (true); 475 if (r->from_type == RULE_GID && groupmember(r->f_gid, cred)) 476 return (true); 477 return (false); 478 } 479 480 static int 481 mac_do_priv_grant(struct ucred *cred, int priv) 482 { 483 struct rule *r; 484 struct prison *pr; 485 struct rules *rule; 486 487 if (do_enabled == 0) 488 return (EPERM); 489 490 rule = find_rules(cred->cr_prison, &pr); 491 TAILQ_FOREACH(r, &rule->head, r_entries) { 492 if (rule_applies(cred, r)) { 493 switch (priv) { 494 case PRIV_CRED_SETGROUPS: 495 case PRIV_CRED_SETUID: 496 prison_unlock(pr); 497 return (0); 498 default: 499 break; 500 } 501 } 502 } 503 prison_unlock(pr); 504 return (EPERM); 505 } 506 507 static int 508 mac_do_check_setgroups(struct ucred *cred, int ngrp, gid_t *groups) 509 { 510 struct rule *r; 511 char *fullpath = NULL; 512 char *freebuf = NULL; 513 struct prison *pr; 514 struct rules *rule; 515 516 if (do_enabled == 0) 517 return (0); 518 if (cred->cr_uid == 0) 519 return (0); 520 521 if (vn_fullpath(curproc->p_textvp, &fullpath, &freebuf) != 0) 522 return (EPERM); 523 if (strcmp(fullpath, "/usr/bin/mdo") != 0) { 524 free(freebuf, M_TEMP); 525 return (EPERM); 526 } 527 free(freebuf, M_TEMP); 528 529 rule = find_rules(cred->cr_prison, &pr); 530 TAILQ_FOREACH(r, &rule->head, r_entries) { 531 if (rule_applies(cred, r)) { 532 prison_unlock(pr); 533 return (0); 534 } 535 } 536 prison_unlock(pr); 537 538 return (EPERM); 539 } 540 541 static int 542 mac_do_check_setuid(struct ucred *cred, uid_t uid) 543 { 544 struct rule *r; 545 int error; 546 char *fullpath = NULL; 547 char *freebuf = NULL; 548 struct prison *pr; 549 struct rules *rule; 550 551 if (do_enabled == 0) 552 return (0); 553 if (cred->cr_uid == uid || cred->cr_uid == 0 || cred->cr_ruid == 0) 554 return (0); 555 556 if (vn_fullpath(curproc->p_textvp, &fullpath, &freebuf) != 0) 557 return (EPERM); 558 if (strcmp(fullpath, "/usr/bin/mdo") != 0) { 559 free(freebuf, M_TEMP); 560 return (EPERM); 561 } 562 free(freebuf, M_TEMP); 563 564 error = EPERM; 565 rule = find_rules(cred->cr_prison, &pr); 566 TAILQ_FOREACH(r, &rule->head, r_entries) { 567 if (r->from_type == RULE_UID) { 568 if (cred->cr_uid != r->f_uid) 569 continue; 570 if (r->to_type == RULE_ANY) { 571 error = 0; 572 break; 573 } 574 if (r->to_type == RULE_UID && uid == r->t_uid) { 575 error = 0; 576 break; 577 } 578 } 579 if (r->from_type == RULE_GID) { 580 if (!groupmember(r->f_gid, cred)) 581 continue; 582 if (r->to_type == RULE_ANY) { 583 error = 0; 584 break; 585 } 586 if (r->to_type == RULE_UID && uid == r->t_uid) { 587 error = 0; 588 break; 589 } 590 } 591 } 592 prison_unlock(pr); 593 return (error); 594 } 595 596 static struct mac_policy_ops do_ops = { 597 .mpo_destroy = mac_do_destroy, 598 .mpo_init = mac_do_init, 599 .mpo_cred_check_setuid = mac_do_check_setuid, 600 .mpo_cred_check_setgroups = mac_do_check_setgroups, 601 .mpo_priv_grant = mac_do_priv_grant, 602 }; 603 604 MAC_POLICY_SET(&do_ops, mac_do, "MAC/do", 605 MPC_LOADTIME_FLAG_UNLOADOK, NULL); 606 MODULE_VERSION(mac_do, 1); 607