1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 * 21 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. 22 * Copyright 2015, Joyent, Inc. 23 * Copyright 2026 Oxide Computer Company 24 */ 25 26 #define _POSIX_PTHREAD_SEMANTICS 1 27 28 #include <sys/ccompile.h> 29 #include <sys/param.h> 30 #include <sys/klpd.h> 31 #include <sys/syscall.h> 32 #include <sys/systeminfo.h> 33 34 #include <alloca.h> 35 #include <ctype.h> 36 #include <deflt.h> 37 #include <door.h> 38 #include <err.h> 39 #include <errno.h> 40 #include <grp.h> 41 #include <priv.h> 42 #include <pthread.h> 43 #include <pwd.h> 44 #include <regex.h> 45 #include <secdb.h> 46 #include <signal.h> 47 #include <stdbool.h> 48 #include <stdio.h> 49 #include <stdlib.h> 50 #include <string.h> 51 #include <syslog.h> 52 #include <unistd.h> 53 54 #include <auth_attr.h> 55 #include <exec_attr.h> 56 #include <prof_attr.h> 57 #include <user_attr.h> 58 59 static int doorfd = -1; 60 61 static size_t repsz, setsz; 62 63 static uid_t get_uid(const char *, boolean_t *, char *); 64 static gid_t get_gid(const char *, boolean_t *, char *); 65 static priv_set_t *get_privset(const char *, boolean_t *, char *); 66 static priv_set_t *get_granted_privs(uid_t); 67 68 /* 69 * Remove the isaexec path of an executable if we can't find the 70 * executable at the first attempt. 71 */ 72 73 static regex_t regc; 74 static boolean_t cansplice = B_TRUE; 75 76 static void 77 init_isa_regex(void) 78 { 79 char *isalist; 80 size_t isalen = 255; /* wild guess */ 81 size_t len; 82 long ret; 83 char *regexpr; 84 char *p; 85 86 /* 87 * Extract the isalist(7) for userland from the kernel. 88 */ 89 isalist = malloc(isalen); 90 do { 91 ret = sysinfo(SI_ISALIST, isalist, isalen); 92 if (ret == -1l) { 93 free(isalist); 94 return; 95 } 96 if (ret > isalen) { 97 isalen = ret; 98 isalist = realloc(isalist, isalen); 99 } else 100 break; 101 } while (isalist != NULL); 102 103 104 if (isalist == NULL) 105 return; 106 107 /* allocate room for the regex + (/())/[^/]*$ + needed \\. */ 108 #define LEFT "(/(" 109 #define RIGHT "))/[^/]*$" 110 111 regexpr = alloca(ret * 2 + sizeof (LEFT RIGHT)); 112 (void) strcpy(regexpr, LEFT); 113 len = strlen(regexpr); 114 115 for (p = isalist; *p; p++) { 116 switch (*p) { 117 case '+': 118 case '|': 119 case '*': 120 case '[': 121 case ']': 122 case '{': 123 case '}': 124 case '\\': 125 regexpr[len++] = '\\'; 126 /* FALLTHROUGH */ 127 default: 128 regexpr[len++] = *p; 129 break; 130 case ' ': 131 case '\t': 132 regexpr[len++] = '|'; 133 break; 134 } 135 } 136 137 free(isalist); 138 regexpr[len] = '\0'; 139 (void) strcat(regexpr, RIGHT); 140 141 if (regcomp(®c, regexpr, REG_EXTENDED) != 0) 142 return; 143 144 cansplice = B_TRUE; 145 } 146 147 #define NMATCH 2 148 149 static boolean_t 150 removeisapath(char *path) 151 { 152 regmatch_t match[NMATCH]; 153 154 if (!cansplice || regexec(®c, path, NMATCH, match, 0) != 0) 155 return (B_FALSE); 156 157 /* 158 * The first match includes the whole matched expression including the 159 * end of the string. The second match includes the "/" + "isa" and 160 * that is the part we need to remove. 161 */ 162 163 if (match[1].rm_so == -1) 164 return (B_FALSE); 165 166 /* match[0].rm_eo == strlen(path) */ 167 (void) memmove(path + match[1].rm_so, path + match[1].rm_eo, 168 match[0].rm_eo - match[1].rm_eo + 1); 169 170 return (B_TRUE); 171 } 172 173 static int 174 register_pfexec(int fd) 175 { 176 int ret = syscall(SYS_privsys, PRIVSYS_PFEXEC_REG, fd); 177 178 return (ret); 179 } 180 181 static void 182 unregister_pfexec(int sig __unused) 183 { 184 if (doorfd != -1) 185 (void) syscall(SYS_privsys, PRIVSYS_PFEXEC_UNREG, doorfd); 186 _exit(0); 187 } 188 189 static int 190 alldigits(const char *s) 191 { 192 int c; 193 194 if (*s == '\0') 195 return (0); 196 197 while ((c = *s++) != '\0') { 198 if (!isdigit(c)) { 199 return (0); 200 } 201 } 202 203 return (1); 204 } 205 206 static uid_t 207 get_uid(const char *v, boolean_t *ok, char *path) 208 { 209 struct passwd *pwd, pwdm; 210 char buf[1024]; 211 212 if (getpwnam_r(v, &pwdm, buf, sizeof (buf), &pwd) == 0 && pwd != NULL) 213 return (pwd->pw_uid); 214 215 if (alldigits(v)) 216 return (atoi(v)); 217 218 *ok = B_FALSE; 219 syslog(LOG_ERR, "%s: %s: unknown username\n", path, v); 220 return ((uid_t)-1); 221 } 222 223 static gid_t 224 get_gid(const char *v, boolean_t *ok, char *path) 225 { 226 struct group *grp, grpm; 227 char *buf; 228 long bufsz; 229 230 /* 231 * The group entry, including the member pointer array which is 232 * constructed inside the caller's buffer, can be large, so size the 233 * buffer using the maximum reported by sysconf(3C). 234 */ 235 if ((bufsz = sysconf(_SC_GETGR_R_SIZE_MAX)) > 0 && 236 (buf = malloc(bufsz)) != NULL) { 237 bool found; 238 239 found = getgrnam_r(v, &grpm, buf, bufsz, &grp) == 0 && 240 grp != NULL; 241 free(buf); 242 if (found) 243 return (grpm.gr_gid); 244 } 245 246 if (alldigits(v)) 247 return (atoi(v)); 248 249 *ok = B_FALSE; 250 syslog(LOG_ERR, "%s: %s: unknown groupname\n", path, v); 251 return ((gid_t)-1); 252 } 253 254 static priv_set_t * 255 get_privset(const char *s, boolean_t *ok, char *path) 256 { 257 priv_set_t *res; 258 259 if ((res = priv_str_to_set(s, ",", NULL)) == NULL) { 260 syslog(LOG_ERR, "%s: %s: bad privilege set\n", path, s); 261 if (ok != NULL) 262 *ok = B_FALSE; 263 } 264 return (res); 265 } 266 267 static int 268 ggp_callback(const char *prof __unused, kva_t *attr, void *ctxt __unused, 269 void *vres) 270 { 271 priv_set_t *res = vres; 272 char *privs; 273 274 if (attr == NULL) 275 return (0); 276 277 /* get privs from this profile */ 278 privs = kva_match(attr, PROFATTR_PRIVS_KW); 279 if (privs != NULL) { 280 priv_set_t *tmp = priv_str_to_set(privs, ",", NULL); 281 if (tmp != NULL) { 282 priv_union(tmp, res); 283 priv_freeset(tmp); 284 } 285 } 286 287 return (0); 288 } 289 290 /* 291 * This routine exists on failure and returns NULL if no granted privileges 292 * are set. 293 */ 294 static priv_set_t * 295 get_granted_privs(uid_t uid) 296 { 297 priv_set_t *res; 298 struct passwd *pwd, pwdm; 299 char buf[1024]; 300 301 if (getpwuid_r(uid, &pwdm, buf, sizeof (buf), &pwd) != 0 || pwd == NULL) 302 return (NULL); 303 304 res = priv_allocset(); 305 if (res == NULL) 306 return (NULL); 307 308 priv_emptyset(res); 309 310 (void) _enum_profs(pwd->pw_name, ggp_callback, NULL, res); 311 312 return (res); 313 } 314 315 static void 316 callback_forced_privs(pfexec_arg_t *pap) 317 { 318 execattr_t *exec; 319 char *value; 320 priv_set_t *fset; 321 void *res = alloca(setsz); 322 323 /* Empty set signifies no forced privileges. */ 324 priv_emptyset(res); 325 326 exec = getexecprof("Forced Privilege", KV_COMMAND, pap->pfa_path, 327 GET_ONE); 328 329 if (exec == NULL && removeisapath(pap->pfa_path)) { 330 exec = getexecprof("Forced Privilege", KV_COMMAND, 331 pap->pfa_path, GET_ONE); 332 } 333 334 if (exec == NULL) { 335 (void) door_return(res, setsz, NULL, 0); 336 return; 337 } 338 339 if ((value = kva_match(exec->attr, EXECATTR_IPRIV_KW)) == NULL || 340 (fset = get_privset(value, NULL, pap->pfa_path)) == NULL) { 341 free_execattr(exec); 342 (void) door_return(res, setsz, NULL, 0); 343 return; 344 } 345 346 priv_copyset(fset, res); 347 priv_freeset(fset); 348 349 free_execattr(exec); 350 (void) door_return(res, setsz, NULL, 0); 351 } 352 353 static void 354 callback_user_privs(pfexec_arg_t *pap) 355 { 356 priv_set_t *gset, *wset; 357 uint32_t res; 358 359 wset = (priv_set_t *)&pap->pfa_buf; 360 gset = get_granted_privs(pap->pfa_uid); 361 362 res = priv_issubset(wset, gset); 363 priv_freeset(gset); 364 365 (void) door_return((char *)&res, sizeof (res), NULL, 0); 366 } 367 368 static void 369 callback_pfexec(pfexec_arg_t *pap) 370 { 371 pfexec_reply_t *res = alloca(repsz); 372 uid_t uid, euid, uuid; 373 gid_t gid, egid; 374 struct passwd pw, *pwd; 375 char buf[1024]; 376 execattr_t *exec = NULL; 377 char *value; 378 priv_set_t *lset, *iset; 379 size_t mysz = repsz - 2 * setsz; 380 char *path = pap->pfa_path; 381 382 /* 383 * Initialize the pfexec_reply_t to a sane state. 384 */ 385 res->pfr_vers = pap->pfa_vers; 386 res->pfr_len = 0; 387 res->pfr_ruid = PFEXEC_NOTSET; 388 res->pfr_euid = PFEXEC_NOTSET; 389 res->pfr_rgid = PFEXEC_NOTSET; 390 res->pfr_egid = PFEXEC_NOTSET; 391 res->pfr_setcred = B_FALSE; 392 res->pfr_scrubenv = B_TRUE; 393 res->pfr_allowed = B_FALSE; 394 res->pfr_ioff = 0; 395 res->pfr_loff = 0; 396 397 uuid = pap->pfa_uid; 398 399 if (getpwuid_r(uuid, &pw, buf, sizeof (buf), &pwd) != 0 || pwd == NULL) 400 goto stdexec; 401 402 exec = getexecuser(pwd->pw_name, KV_COMMAND, path, GET_ONE); 403 404 if ((exec == NULL || exec->attr == NULL) && removeisapath(path)) { 405 free_execattr(exec); 406 exec = getexecuser(pwd->pw_name, KV_COMMAND, path, GET_ONE); 407 } 408 409 if (exec == NULL) { 410 res->pfr_allowed = B_FALSE; 411 goto ret; 412 } 413 414 if (exec->attr == NULL) 415 goto stdexec; 416 417 /* Found in execattr, so clearly we can use it */ 418 res->pfr_allowed = B_TRUE; 419 420 uid = euid = (uid_t)-1; 421 gid = egid = (gid_t)-1; 422 lset = iset = NULL; 423 424 /* 425 * If there's an error in parsing uid, gid, privs, then return 426 * failure. 427 */ 428 if ((value = kva_match(exec->attr, EXECATTR_UID_KW)) != NULL) 429 euid = uid = get_uid(value, &res->pfr_allowed, path); 430 431 if ((value = kva_match(exec->attr, EXECATTR_GID_KW)) != NULL) 432 egid = gid = get_gid(value, &res->pfr_allowed, path); 433 434 if ((value = kva_match(exec->attr, EXECATTR_EUID_KW)) != NULL) 435 euid = get_uid(value, &res->pfr_allowed, path); 436 437 if ((value = kva_match(exec->attr, EXECATTR_EGID_KW)) != NULL) 438 egid = get_gid(value, &res->pfr_allowed, path); 439 440 if ((value = kva_match(exec->attr, EXECATTR_LPRIV_KW)) != NULL) 441 lset = get_privset(value, &res->pfr_allowed, path); 442 443 if ((value = kva_match(exec->attr, EXECATTR_IPRIV_KW)) != NULL) 444 iset = get_privset(value, &res->pfr_allowed, path); 445 446 /* 447 * Remove LD_* variables in the kernel when the runtime linker might 448 * use them later on because the uids are equal. 449 */ 450 res->pfr_scrubenv = (uid != (uid_t)-1 && euid == uid) || 451 (gid != (gid_t)-1 && egid == gid) || iset != NULL; 452 453 res->pfr_euid = euid; 454 res->pfr_ruid = uid; 455 res->pfr_egid = egid; 456 res->pfr_rgid = gid; 457 458 /* Now add the privilege sets */ 459 res->pfr_ioff = res->pfr_loff = 0; 460 if (iset != NULL) { 461 res->pfr_ioff = mysz; 462 priv_copyset(iset, PFEXEC_REPLY_IPRIV(res)); 463 mysz += setsz; 464 priv_freeset(iset); 465 } 466 if (lset != NULL) { 467 res->pfr_loff = mysz; 468 priv_copyset(lset, PFEXEC_REPLY_LPRIV(res)); 469 mysz += setsz; 470 priv_freeset(lset); 471 } 472 473 res->pfr_setcred = uid != (uid_t)-1 || euid != (uid_t)-1 || 474 egid != (gid_t)-1 || gid != (gid_t)-1 || iset != NULL || 475 lset != NULL; 476 477 /* If the real uid changes, we stop running under a profile shell */ 478 res->pfr_clearflag = uid != (uid_t)-1 && uid != uuid; 479 free_execattr(exec); 480 ret: 481 (void) door_return((char *)res, mysz, NULL, 0); 482 return; 483 484 stdexec: 485 free_execattr(exec); 486 487 res->pfr_scrubenv = B_FALSE; 488 res->pfr_setcred = B_FALSE; 489 res->pfr_allowed = B_TRUE; 490 491 (void) door_return((char *)res, mysz, NULL, 0); 492 } 493 494 static void 495 callback(void *cookie __unused, char *argp, size_t asz, 496 door_desc_t *dp __unused, uint_t ndesc __unused) 497 { 498 pfexec_arg_t *pap = (pfexec_arg_t *)argp; 499 500 if (asz < sizeof (pfexec_arg_t) || pap->pfa_vers != PFEXEC_ARG_VERS) { 501 (void) door_return(NULL, 0, NULL, 0); 502 return; 503 } 504 505 switch (pap->pfa_call) { 506 case PFEXEC_EXEC_ATTRS: 507 callback_pfexec(pap); 508 break; 509 case PFEXEC_FORCED_PRIVS: 510 callback_forced_privs(pap); 511 break; 512 case PFEXEC_USER_PRIVS: 513 callback_user_privs(pap); 514 break; 515 default: 516 syslog(LOG_ERR, "Bad Call: %d\n", pap->pfa_call); 517 break; 518 } 519 520 /* 521 * If the door_return(ptr, size, NULL, 0) fails, make sure we 522 * don't lose server threads. 523 */ 524 (void) door_return(NULL, 0, NULL, 0); 525 } 526 527 /* 528 * Door server threads. The default door server thread creation function in 529 * libc places no limit on the number of threads it will create and gives 530 * each one a default sized (1 MiB) stack. Instead, create a private door 531 * with a fixed pool of server threads that have smaller stacks. The door 532 * is created with DOOR_NO_DEPLETION_CB so the pool never grows beyond its 533 * initial size; the kernel queues further door invocations until a server 534 * thread becomes available. 535 * 536 * The stack size must accommodate libsecdb's profile enumeration, which 537 * keeps MAXPROFS-sized pointer arrays and copies of profile lists on 538 * the stack, with headroom for nested profiles. 539 */ 540 #define DOOR_THREADS 16 541 #define DOOR_THREAD_STACKSIZE (256 * 1024) 542 543 /* Initialised in main() before the door is created, then never modified. */ 544 static pthread_attr_t door_attr; 545 546 /* 547 * Since the door uses DOOR_NO_DEPLETION_CB, this is only called from 548 * door_xcreate() to populate the initial thread pool. 549 */ 550 static int 551 create_door_thread(door_info_t *dip __unused, void *(*startf)(void *), 552 void *startfarg, void *cookie __unused) 553 { 554 if (pthread_create(NULL, &door_attr, startf, startfarg) != 0) 555 return (-1); 556 return (1); 557 } 558 559 int 560 main(void) 561 { 562 const priv_impl_info_t *info; 563 int ret; 564 565 (void) signal(SIGINT, unregister_pfexec); 566 (void) signal(SIGQUIT, unregister_pfexec); 567 (void) signal(SIGTERM, unregister_pfexec); 568 (void) signal(SIGHUP, unregister_pfexec); 569 570 info = getprivimplinfo(); 571 if (info == NULL) 572 exit(1); 573 574 if (fork() > 0) 575 _exit(0); 576 577 openlog("pfexecd", LOG_PID, LOG_DAEMON); 578 setsz = info->priv_setsize * sizeof (priv_chunk_t); 579 repsz = 2 * setsz + sizeof (pfexec_reply_t); 580 581 init_isa_regex(); 582 583 if ((ret = pthread_attr_init(&door_attr)) != 0 || 584 (ret = pthread_attr_setdetachstate(&door_attr, 585 PTHREAD_CREATE_DETACHED)) != 0 || 586 (ret = pthread_attr_setstacksize(&door_attr, 587 DOOR_THREAD_STACKSIZE)) != 0) { 588 errc(EXIT_FAILURE, ret, 589 "failed to configure door thread attributes"); 590 } 591 592 doorfd = door_xcreate(callback, NULL, 593 DOOR_REFUSE_DESC | DOOR_NO_DEPLETION_CB, create_door_thread, 594 NULL, NULL, DOOR_THREADS); 595 596 if (doorfd == -1 || register_pfexec(doorfd) != 0) { 597 perror("doorfd"); 598 exit(1); 599 } 600 601 while (1) 602 (void) sigpause(SIGINT); 603 604 return (0); 605 } 606