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