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