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