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