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