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