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