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