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, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2005 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 28 29 #include <sys/task.h> 30 #include <sys/types.h> 31 #include <unistd.h> 32 33 #include <ctype.h> 34 #include <project.h> 35 #include <rctl.h> 36 #include <secdb.h> 37 #include <signal.h> 38 #include <stdlib.h> 39 #include <string.h> 40 #include <nss_dbdefs.h> 41 #include <pwd.h> 42 #include <pool.h> 43 #include <libproc.h> 44 #include <priv.h> 45 #include <priv_utils.h> 46 #include <zone.h> 47 #include <sys/pool.h> 48 #include <sys/pool_impl.h> 49 50 static void 51 xstrtolower(char *s) 52 { 53 for (; *s != '\0'; s++) 54 *s = tolower(*s); 55 } 56 57 static void 58 remove_spaces(char *s) 59 { 60 char *current; 61 char *next; 62 63 current = next = s; 64 65 while (*next != '\0') { 66 while (isspace(*next)) 67 next++; 68 *current++ = *next++; 69 } 70 *current = '\0'; 71 } 72 73 int 74 build_rctlblk(rctlblk_t *blk, int comp_num, char *component) 75 { 76 char *signam; 77 int sig = 0; 78 uint_t act = rctlblk_get_local_action(blk, &sig); 79 80 if (comp_num == 0) { 81 /* 82 * Setting privilege level for resource control block. 83 */ 84 xstrtolower(component); 85 86 if (strcmp("basic", component) == 0) { 87 rctlblk_set_privilege(blk, RCPRIV_BASIC); 88 return (0); 89 } 90 91 if (strcmp("priv", component) == 0 || 92 strcmp("privileged", component) == 0) { 93 rctlblk_set_privilege(blk, RCPRIV_PRIVILEGED); 94 return (0); 95 } 96 97 return (-1); 98 } 99 100 if (comp_num == 1) { 101 102 /* 103 * Setting value for resource control block. 104 */ 105 unsigned long long val; 106 char *t; 107 108 /* Negative numbers are not allowed */ 109 if (strchr(component, '-') != NULL) 110 return (-1); 111 112 errno = 0; 113 val = strtoull(component, &t, 10); 114 if (errno != 0 || t == component || *t != '\0') 115 return (-1); 116 117 rctlblk_set_value(blk, (rctl_qty_t)val); 118 return (0); 119 } 120 121 /* 122 * Setting one or more actions on this resource control block. 123 */ 124 if (comp_num >= 2) { 125 if (strcmp("none", component) == 0) { 126 rctlblk_set_local_action(blk, 0, 0); 127 return (0); 128 } 129 130 if (strcmp("deny", component) == 0) { 131 act |= RCTL_LOCAL_DENY; 132 133 rctlblk_set_local_action(blk, act, sig); 134 135 return (0); 136 } 137 138 /* 139 * The last, and trickiest, form of action is the signal 140 * specification. 141 */ 142 if ((signam = strchr(component, '=')) == NULL) 143 return (-1); 144 145 *signam++ = '\0'; 146 147 if (strcmp("sig", component) == 0 || 148 strcmp("signal", component) == 0) { 149 if (strncmp("SIG", signam, 3) == 0) 150 signam += 3; 151 152 if (str2sig(signam, &sig) == -1) 153 return (-1); 154 155 act |= RCTL_LOCAL_SIGNAL; 156 157 rctlblk_set_local_action(blk, act, sig); 158 159 return (0); 160 } 161 } 162 return (-1); 163 } 164 165 /* 166 * States: 167 */ 168 #define INPAREN 0x1 169 170 /* 171 * Errors: 172 */ 173 #define SETFAILED (-1) 174 #define COMPLETE 1 175 #define NESTING 2 176 #define UNCLOSED 3 177 #define CLOSEBEFOREOPEN 4 178 #define BADSPEC 5 179 180 static int 181 rctl_set(char *ctl_name, char *val, struct ps_prochandle *Pr) 182 { 183 int error = 0; 184 uint_t component = 0; 185 int valuecount = 0; 186 uint_t state = 0; 187 char *component_head; 188 rctlblk_t *blk; 189 190 remove_spaces(val); 191 if ((blk = malloc(rctlblk_size())) == NULL) { 192 return (SETFAILED); 193 } 194 195 /* 196 * Tear down everything with this ctl name. 197 */ 198 while (pr_getrctl(Pr, ctl_name, NULL, blk, RCTL_FIRST) != -1 && 199 rctlblk_get_privilege(blk) != RCPRIV_SYSTEM) { 200 (void) pr_setrctl(Pr, ctl_name, NULL, blk, RCTL_DELETE); 201 } 202 203 /* 204 * Set initial local action based on global deny properties. 205 */ 206 rctlblk_set_privilege(blk, RCPRIV_PRIVILEGED); 207 rctlblk_set_value(blk, 0); 208 rctlblk_set_local_flags(blk, 0); 209 if (rctlblk_get_global_flags(blk) & RCTL_GLOBAL_DENY_ALWAYS) 210 rctlblk_set_local_action(blk, RCTL_LOCAL_DENY, 0); 211 else 212 rctlblk_set_local_action(blk, RCTL_LOCAL_NOACTION, 0); 213 214 215 for (; ; val++) { 216 switch (*val) { 217 case '(': 218 if (state & INPAREN) { 219 error = NESTING; 220 break; 221 } 222 223 state |= INPAREN; 224 component_head = (char *)val + 1; 225 226 break; 227 case ')': 228 if (state & INPAREN) { 229 *val = '\0'; 230 if (component < 2) { 231 error = BADSPEC; 232 break; 233 } 234 if (build_rctlblk(blk, component, 235 component_head) == -1) { 236 error = BADSPEC; 237 break; 238 } 239 state &= ~INPAREN; 240 component = 0; 241 valuecount++; 242 if (pr_setrctl(Pr, ctl_name, NULL, blk, 243 RCTL_INSERT) == -1) 244 error = SETFAILED; 245 246 /* re-initialize block */ 247 rctlblk_set_privilege(blk, 248 RCPRIV_PRIVILEGED); 249 rctlblk_set_value(blk, 0); 250 rctlblk_set_local_flags(blk, 0); 251 if (rctlblk_get_global_flags(blk) & 252 RCTL_GLOBAL_DENY_ALWAYS) 253 rctlblk_set_local_action(blk, 254 RCTL_LOCAL_DENY, 0); 255 else 256 rctlblk_set_local_action(blk, 257 RCTL_LOCAL_NOACTION, 0); 258 } else { 259 error = CLOSEBEFOREOPEN; 260 } 261 break; 262 case ',': 263 if (state & INPAREN) { 264 *val = '\0'; 265 if (build_rctlblk(blk, component, 266 component_head) == -1) 267 error = BADSPEC; 268 269 component++; 270 component_head = (char *)val + 1; 271 272 } 273 break; 274 case '\0': 275 if (valuecount == 0) 276 error = BADSPEC; 277 else if (state & INPAREN) 278 error = UNCLOSED; 279 else 280 error = COMPLETE; 281 break; 282 default: 283 if (!(state & INPAREN)) 284 error = BADSPEC; 285 break; 286 } 287 288 if (error) 289 break; 290 } 291 292 free(blk); 293 294 if (valuecount == 0) 295 error = BADSPEC; 296 297 if (error != COMPLETE) 298 return (error); 299 300 return (0); 301 } 302 303 static int 304 rctlwalkfunc(const char *name, void *data) 305 { 306 307 if (strcmp(name, (char *)data) == 0) 308 return (-1); 309 else 310 return (0); 311 312 } 313 314 /* 315 * This routine determines if /dev/pool device is present on the system and 316 * pools are currently enabled. We want to do this directly from libproject 317 * without using libpool's pool_get_status() routine because pools could be 318 * completely removed from the system. Return 1 if pools are enabled, or 319 * 0 otherwise. When used inside local zones, always pretend that pools 320 * are disabled because binding is not allowed and we're already in the 321 * right pool. 322 */ 323 static int 324 pools_enabled(void) 325 { 326 pool_status_t status; 327 int fd; 328 329 if (getzoneid() != GLOBAL_ZONEID) 330 return (0); 331 if ((fd = open("/dev/pool", O_RDONLY)) < 0) 332 return (0); 333 if (ioctl(fd, POOL_STATUSQ, &status) < 0) { 334 (void) close(fd); 335 return (0); 336 } 337 (void) close(fd); 338 return (status.ps_io_state); 339 } 340 341 /* 342 * A pool_name of NULL means to attempt to bind to the default pool. 343 * If the "force" flag is non-zero, the value of "system.bind-default" will be 344 * ignored, and the process will be bound to the default pool if one exists. 345 */ 346 static int 347 bind_to_pool(const char *pool_name, pid_t pid, int force) 348 { 349 pool_value_t *pvals[] = { NULL, NULL }; 350 pool_t **pools; 351 uint_t nelem; 352 uchar_t bval; 353 pool_conf_t *conf; 354 const char *nm; 355 int retval; 356 357 if ((conf = pool_conf_alloc()) == NULL) 358 return (-1); 359 if (pool_conf_open(conf, pool_dynamic_location(), PO_RDONLY) < 0) { 360 /* 361 * Pools configuration file is corrupted; allow logins. 362 */ 363 pool_conf_free(conf); 364 return (0); 365 } 366 if (pool_name != NULL && pool_get_pool(conf, pool_name) != NULL) { 367 /* 368 * There was a project.pool entry, and the pool it refers to 369 * is a valid (active) pool. 370 */ 371 (void) pool_conf_close(conf); 372 pool_conf_free(conf); 373 if (pool_set_binding(pool_name, P_PID, pid) != PO_SUCCESS) { 374 if (pool_error() != POE_SYSTEM) 375 errno = EINVAL; 376 return (-1); 377 } 378 return (0); 379 } 380 381 /* 382 * Bind to the pool with 'pool.default' = 'true' if 383 * 'system.bind-default' = 'true'. 384 */ 385 if ((pvals[0] = pool_value_alloc()) == NULL) { 386 pool_conf_close(conf); 387 pool_conf_free(conf); 388 return (-1); 389 } 390 if (!force && pool_get_property(conf, pool_conf_to_elem(conf), 391 "system.bind-default", pvals[0]) != POC_BOOL || 392 pool_value_get_bool(pvals[0], &bval) != PO_SUCCESS || 393 bval == PO_FALSE) { 394 pool_value_free(pvals[0]); 395 pool_conf_close(conf); 396 pool_conf_free(conf); 397 errno = pool_name == NULL ? EACCES : ESRCH; 398 return (-1); 399 } 400 (void) pool_value_set_name(pvals[0], "pool.default"); 401 pool_value_set_bool(pvals[0], PO_TRUE); 402 if ((pools = pool_query_pools(conf, &nelem, pvals)) == NULL) { 403 /* 404 * No default pools exist. 405 */ 406 pool_value_free(pvals[0]); 407 pool_conf_close(conf); 408 pool_conf_free(conf); 409 errno = pool_name == NULL ? EACCES : ESRCH; 410 return (-1); 411 } 412 if (nelem != 1 || 413 pool_get_property(conf, pool_to_elem(conf, pools[0]), "pool.name", 414 pvals[0]) != POC_STRING) { 415 /* 416 * Configuration is invalid. 417 */ 418 free(pools); 419 pool_value_free(pvals[0]); 420 (void) pool_conf_close(conf); 421 pool_conf_free(conf); 422 return (0); 423 } 424 free(pools); 425 (void) pool_conf_close(conf); 426 pool_conf_free(conf); 427 (void) pool_value_get_string(pvals[0], &nm); 428 if (pool_set_binding(nm, P_PID, pid) != PO_SUCCESS) { 429 if (pool_error() != POE_SYSTEM) 430 errno = EINVAL; 431 retval = -1; 432 } else { 433 retval = 0; 434 } 435 pool_value_free(pvals[0]); 436 return (retval); 437 } 438 439 /* 440 * Changes the assigned project, task and resource pool of a stopped target 441 * process. 442 * 443 * We may not have access to the project table if our target process is in 444 * getprojbyname()'s execution path. Similarly, we may not be able to get user 445 * information if the target process is in getpwnam()'s execution path. Thus we 446 * give the caller the option of skipping these checks by providing a pointer to 447 * a pre-validated project structure in proj (whose name matches project_name) 448 * and taking responsibility for ensuring that the target process' owner is a 449 * member of the target project. 450 * 451 * Callers of this function should always provide a pre-validated project 452 * structure in proj unless they can be sure that the target process will never 453 * be in setproject_proc()'s execution path. 454 */ 455 456 projid_t 457 setproject_proc(const char *project_name, const char *user_name, int flags, 458 pid_t pid, struct ps_prochandle *Pr, struct project *proj) 459 { 460 char pwdbuf[NSS_BUFLEN_PASSWD]; 461 char prbuf[PROJECT_BUFSZ]; 462 projid_t projid; 463 struct passwd pwd; 464 int i; 465 int unknown = 0; 466 int ret = 0; 467 kva_t *kv_array; 468 struct project local_proj; /* space to store proj if not provided */ 469 470 if (project_name != NULL) { 471 /* 472 * Sanity checks. 473 */ 474 if (strcmp(project_name, "") == 0 || 475 user_name == NULL) { 476 errno = EINVAL; 477 return (SETPROJ_ERR_TASK); 478 } 479 480 /* 481 * If proj is NULL, acquire project information to ensure that 482 * project_name is a valid project, and confirm that user_name 483 * exists and is a member of the specified project. 484 */ 485 if (proj == NULL) { 486 if ((proj = getprojbyname(project_name, &local_proj, 487 prbuf, PROJECT_BUFSZ)) == NULL) { 488 errno = ESRCH; 489 return (SETPROJ_ERR_TASK); 490 } 491 492 if (getpwnam_r(user_name, &pwd, 493 pwdbuf, NSS_BUFLEN_PASSWD) == NULL) { 494 errno = ESRCH; 495 return (SETPROJ_ERR_TASK); 496 } 497 /* 498 * Root can join any project. 499 */ 500 if (pwd.pw_uid != (uid_t)0 && 501 !inproj(user_name, project_name, prbuf, 502 PROJECT_BUFSZ)) { 503 errno = ESRCH; 504 return (SETPROJ_ERR_TASK); 505 } 506 } 507 projid = proj->pj_projid; 508 } else { 509 projid = getprojid(); 510 } 511 512 /* 513 * Only bind to a pool if pools are configured. 514 */ 515 if (pools_enabled() == 1) { 516 const char *pool_name = NULL; 517 char *old_pool_name; 518 int taskflags = flags; 519 /* 520 * Attempt to bind to pool before calling 521 * settaskid(). 522 */ 523 if ((kv_array = _str2kva(proj->pj_attr, KV_ASSIGN, 524 KV_DELIMITER)) != NULL) { 525 for (i = 0; i < kv_array->length; i++) { 526 if (strcmp(kv_array->data[i].key, 527 "project.pool") == 0) { 528 pool_name = kv_array->data[i].value; 529 break; 530 } 531 if (strcmp(kv_array->data[i].key, 532 "task.final") == 0) { 533 taskflags |= TASK_FINAL; 534 } 535 } 536 } 537 538 old_pool_name = pool_get_binding(pid); 539 if (bind_to_pool(pool_name, pid, 0) != 0) { 540 if (old_pool_name) 541 free(old_pool_name); 542 _kva_free(kv_array); 543 return (SETPROJ_ERR_POOL); 544 } 545 if (pr_settaskid(Pr, projid, taskflags) == -1) { 546 int saved_errno = errno; 547 548 /* 549 * Undo pool binding. 550 */ 551 (void) bind_to_pool(old_pool_name, pid, 1); 552 if (old_pool_name) 553 free(old_pool_name); 554 _kva_free(kv_array); 555 /* 556 * Restore errno 557 */ 558 errno = saved_errno; 559 return (SETPROJ_ERR_TASK); 560 } 561 if (old_pool_name) 562 free(old_pool_name); 563 } else { 564 /* 565 * Pools are not configured, so simply create new task. 566 */ 567 if (pr_settaskid(Pr, projid, flags) == -1) 568 return (SETPROJ_ERR_TASK); 569 kv_array = _str2kva(proj->pj_attr, KV_ASSIGN, KV_DELIMITER); 570 } 571 572 if (project_name == NULL) { 573 /* 574 * In the case that we are starting a new task in the 575 * current project, we are finished, since the current 576 * resource controls will still apply. (Implicit behaviour: 577 * a project must be entirely logged out before name 578 * service changes will take effect.) 579 */ 580 _kva_free(kv_array); 581 return (projid); 582 } 583 584 if (kv_array == NULL) 585 return (0); 586 587 for (i = 0; i < kv_array->length; i++) { 588 /* 589 * Providing a special, i.e. a non-resource control, key? Then 590 * parse that key here and end with "continue;". 591 */ 592 593 /* 594 * For generic bindings, the kernel performs the binding, as 595 * these are resource controls advertised by kernel subsystems. 596 */ 597 598 /* 599 * Check for known attribute name. 600 */ 601 errno = 0; 602 if (rctl_walk(rctlwalkfunc, (void *)kv_array->data[i].key) 603 == 0) 604 continue; 605 if (errno) { 606 _kva_free(kv_array); 607 return (SETPROJ_ERR_TASK); 608 } 609 610 ret = rctl_set(kv_array->data[i].key, 611 kv_array->data[i].value, Pr); 612 613 if (ret && unknown == 0) { 614 /* 615 * We only report the first failure. 616 */ 617 unknown = i + 1; 618 } 619 620 if (ret && ret != SETFAILED) { 621 /* 622 * We abort if we couldn't set a component, but if 623 * it's merely that the system didn't recognize it, we 624 * continue, as this could be a third party attribute. 625 */ 626 break; 627 } 628 } 629 _kva_free(kv_array); 630 631 return (unknown); 632 } 633 634 projid_t 635 setproject(const char *project_name, const char *user_name, int flags) 636 { 637 return (setproject_proc(project_name, user_name, flags, P_MYID, NULL, 638 NULL)); 639 } 640 641 642 priv_set_t * 643 setproject_initpriv(void) 644 { 645 static priv_t taskpriv = PRIV_PROC_TASKID; 646 static priv_t rctlpriv = PRIV_SYS_RESOURCE; 647 static priv_t poolpriv = PRIV_SYS_RES_CONFIG; 648 static priv_t schedpriv = PRIV_PROC_PRIOCNTL; 649 int res; 650 651 priv_set_t *nset; 652 653 if (getzoneid() == GLOBAL_ZONEID) { 654 res = __init_suid_priv(0, taskpriv, rctlpriv, poolpriv, 655 schedpriv, (char *)NULL); 656 } else { 657 res = __init_suid_priv(0, taskpriv, rctlpriv, (char *)NULL); 658 } 659 660 if (res != 0) 661 return (NULL); 662 663 nset = priv_allocset(); 664 if (nset != NULL) { 665 priv_emptyset(nset); 666 (void) priv_addset(nset, taskpriv); 667 (void) priv_addset(nset, rctlpriv); 668 /* 669 * Only need these if we need to change pools, which can 670 * only happen if the target is in the global zone. Rather 671 * than checking the target's zone just check our own 672 * (since if we're in a non-global zone we won't be able 673 * to control processes in other zones). 674 */ 675 if (getzoneid() == GLOBAL_ZONEID) { 676 (void) priv_addset(nset, poolpriv); 677 (void) priv_addset(nset, schedpriv); 678 } 679 } 680 return (nset); 681 } 682