/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2018, Joyent, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static void xstrtolower(char *s) { for (; *s != '\0'; s++) *s = tolower(*s); } static void remove_spaces(char *s) { char *current; char *next; current = next = s; while (*next != '\0') { while (isspace(*next)) next++; *current++ = *next++; } *current = '\0'; } int build_rctlblk(rctlblk_t *blk, int comp_num, char *component) { char *signam; int sig = 0; uint_t act = rctlblk_get_local_action(blk, &sig); if (comp_num == 0) { /* * Setting privilege level for resource control block. */ xstrtolower(component); if (strcmp("basic", component) == 0) { rctlblk_set_privilege(blk, RCPRIV_BASIC); return (0); } if (strcmp("priv", component) == 0 || strcmp("privileged", component) == 0) { rctlblk_set_privilege(blk, RCPRIV_PRIVILEGED); return (0); } return (-1); } if (comp_num == 1) { /* * Setting value for resource control block. */ unsigned long long val; char *t; /* Negative numbers are not allowed */ if (strchr(component, '-') != NULL) return (-1); errno = 0; val = strtoull(component, &t, 10); if (errno != 0 || t == component || *t != '\0') return (-1); rctlblk_set_value(blk, (rctl_qty_t)val); return (0); } /* * Setting one or more actions on this resource control block. */ if (comp_num >= 2) { if (strcmp("none", component) == 0) { rctlblk_set_local_action(blk, 0, 0); return (0); } if (strcmp("deny", component) == 0) { act |= RCTL_LOCAL_DENY; rctlblk_set_local_action(blk, act, sig); return (0); } /* * The last, and trickiest, form of action is the signal * specification. */ if ((signam = strchr(component, '=')) == NULL) return (-1); *signam++ = '\0'; if (strcmp("sig", component) == 0 || strcmp("signal", component) == 0) { if (strncmp("SIG", signam, 3) == 0) signam += 3; if (str2sig(signam, &sig) == -1) return (-1); act |= RCTL_LOCAL_SIGNAL; rctlblk_set_local_action(blk, act, sig); return (0); } } return (-1); } /* * States: */ #define INPAREN 0x1 /* * Errors: */ #define SETFAILED (-1) #define COMPLETE 1 #define NESTING 2 #define UNCLOSED 3 #define CLOSEBEFOREOPEN 4 #define BADSPEC 5 static void reinit_blk(rctlblk_t *blk, int local_action) { rctlblk_set_privilege(blk, RCPRIV_PRIVILEGED); rctlblk_set_value(blk, 0); rctlblk_set_local_flags(blk, 0); rctlblk_set_local_action(blk, local_action, 0); } static int rctl_set(char *ctl_name, char *val, struct ps_prochandle *Pr, int flags) { int error = 0; uint_t component = 0; int valuecount = 0; uint_t state = 0; char *component_head; rctlblk_t *blk; rctlblk_t *ablk; int project_entity = 0; int count = 0; char *tmp; int local_act; rctlblk_t *rnext; int teardown_basic = 0; int teardown_priv = 0; /* We cannot modify a zone resource control */ if (strncmp(ctl_name, "zone.", strlen("zone.")) == 0) { return (SETFAILED); } remove_spaces(val); if (strncmp(ctl_name, "project.", strlen("project.")) == 0) { project_entity = 1; } else if ((strncmp(ctl_name, "process.", strlen("process.")) != 0) && (strncmp(ctl_name, "task.", strlen("task.")) != 0)) { return (SETFAILED); } /* Determine how many attributes we'll be setting */ for (tmp = val; *tmp != '\0'; tmp++) { if (*tmp == '(') count++; } /* Allocate sufficient memory for rctl blocks */ if ((count == 0) || ((ablk = (rctlblk_t *)malloc(rctlblk_size() * count)) == NULL)) { return (SETFAILED); } blk = ablk; /* * In order to set the new rctl's local_action, we'll need the * current value of global_flags. We obtain global_flags by * performing a pr_getrctl(). * * The ctl_name has been verified as valid, so we have no reason * to suspect that pr_getrctl() will return an error. */ (void) pr_getrctl(Pr, ctl_name, NULL, blk, RCTL_FIRST); /* * Set initial local action based on global deny properties. */ rctlblk_set_privilege(blk, RCPRIV_PRIVILEGED); rctlblk_set_value(blk, 0); rctlblk_set_local_flags(blk, 0); if (rctlblk_get_global_flags(blk) & RCTL_GLOBAL_DENY_ALWAYS) local_act = RCTL_LOCAL_DENY; else local_act = RCTL_LOCAL_NOACTION; rctlblk_set_local_action(blk, local_act, 0); for (; ; val++) { switch (*val) { case '(': if (state & INPAREN) { error = NESTING; break; } state |= INPAREN; component_head = (char *)val + 1; break; case ')': if (state & INPAREN) { *val = '\0'; if (component < 2) { error = BADSPEC; break; } if (build_rctlblk(blk, component, component_head) == -1) { error = BADSPEC; break; } state &= ~INPAREN; component = 0; valuecount++; if (project_entity && (rctlblk_get_privilege(blk) == RCPRIV_BASIC)) { error = SETFAILED; } else { if (rctlblk_get_privilege(blk) == RCPRIV_BASIC) teardown_basic = 1; if (rctlblk_get_privilege(blk) == RCPRIV_PRIVILEGED) teardown_priv = 1; if (valuecount > count) { free(ablk); return (SETFAILED); } if (valuecount != count) { blk = RCTLBLK_INC(ablk, valuecount); /* re-initialize blk */ reinit_blk(blk, local_act); } } } else { error = CLOSEBEFOREOPEN; } break; case ',': if (state & INPAREN) { *val = '\0'; if (build_rctlblk(blk, component, component_head) == -1) error = BADSPEC; component++; component_head = (char *)val + 1; } break; case '\0': if (valuecount == 0) error = BADSPEC; else if (state & INPAREN) error = UNCLOSED; else error = COMPLETE; break; default: if (!(state & INPAREN)) error = BADSPEC; break; } if (error) break; } /* ablk points to array of rctlblk_t */ if (valuecount == 0) error = BADSPEC; if (error != COMPLETE) { free(ablk); return (error); } /* teardown rctls if required */ if (!project_entity) { if ((rnext = (rctlblk_t *)malloc(rctlblk_size())) == NULL) { free(ablk); return (SETFAILED); } restart: if (pr_getrctl(Pr, ctl_name, NULL, rnext, RCTL_FIRST) == 0) { while (1) { if ((rctlblk_get_privilege(rnext) == RCPRIV_PRIVILEGED) && (teardown_priv == 1)) { (void) pr_setrctl(Pr, ctl_name, NULL, rnext, RCTL_DELETE); goto restart; } if ((rctlblk_get_privilege(rnext) == RCPRIV_BASIC) && (teardown_basic == 1)) { (void) pr_setrctl(Pr, ctl_name, NULL, rnext, RCTL_DELETE); goto restart; } if (pr_getrctl(Pr, ctl_name, rnext, rnext, RCTL_NEXT) == -1) break; } } free(rnext); } /* set rctls */ blk = ablk; if (project_entity) { if (pr_setprojrctl(Pr, ctl_name, blk, count, flags) == -1) error = SETFAILED; } else { valuecount = 0; while (valuecount < count) { if (pr_setrctl(Pr, ctl_name, NULL, blk, RCTL_INSERT) == -1) { error = SETFAILED; break; } valuecount++; blk = RCTLBLK_INC(ablk, valuecount); } } free(ablk); if (error != COMPLETE) return (error); return (0); } static int rctlwalkfunc(const char *name, void *data) { if (strcmp(name, (char *)data) == 0) return (-1); else return (0); } /* * This routine determines if /dev/pool device is present on the system and * pools are currently enabled. We want to do this directly from libproject * without using libpool's pool_get_status() routine because pools could be * completely removed from the system. Return 1 if pools are enabled, or * 0 otherwise. When used inside local zones, always pretend that pools * are disabled because binding is not allowed and we're already in the * right pool. */ static int pools_enabled(void) { pool_status_t status; int fd; if (getzoneid() != GLOBAL_ZONEID) return (0); if ((fd = open("/dev/pool", O_RDONLY)) < 0) return (0); if (ioctl(fd, POOL_STATUSQ, &status) < 0) { (void) close(fd); return (0); } (void) close(fd); return (status.ps_io_state); } /* * A pool_name of NULL means to attempt to bind to the default pool. * If the "force" flag is non-zero, the value of "system.bind-default" will be * ignored, and the process will be bound to the default pool if one exists. */ static int bind_to_pool(const char *pool_name, pid_t pid, int force) { pool_value_t *pvals[] = { NULL, NULL }; pool_t **pools; uint_t nelem; uchar_t bval; pool_conf_t *conf; const char *nm; int retval; if ((conf = pool_conf_alloc()) == NULL) return (-1); if (pool_conf_open(conf, pool_dynamic_location(), PO_RDONLY) < 0) { /* * Pools configuration file is corrupted; allow logins. */ pool_conf_free(conf); return (0); } if (pool_name != NULL && pool_get_pool(conf, pool_name) != NULL) { /* * There was a project.pool entry, and the pool it refers to * is a valid (active) pool. */ (void) pool_conf_close(conf); pool_conf_free(conf); if (pool_set_binding(pool_name, P_PID, pid) != PO_SUCCESS) { if (pool_error() != POE_SYSTEM) errno = EINVAL; return (-1); } return (0); } /* * Bind to the pool with 'pool.default' = 'true' if * 'system.bind-default' = 'true'. */ if ((pvals[0] = pool_value_alloc()) == NULL) { (void) pool_conf_close(conf); pool_conf_free(conf); return (-1); } if (!force && pool_get_property(conf, pool_conf_to_elem(conf), "system.bind-default", pvals[0]) != POC_BOOL || pool_value_get_bool(pvals[0], &bval) != PO_SUCCESS || bval == PO_FALSE) { pool_value_free(pvals[0]); (void) pool_conf_close(conf); pool_conf_free(conf); errno = pool_name == NULL ? EACCES : ESRCH; return (-1); } (void) pool_value_set_name(pvals[0], "pool.default"); pool_value_set_bool(pvals[0], PO_TRUE); if ((pools = pool_query_pools(conf, &nelem, pvals)) == NULL) { /* * No default pools exist. */ pool_value_free(pvals[0]); (void) pool_conf_close(conf); pool_conf_free(conf); errno = pool_name == NULL ? EACCES : ESRCH; return (-1); } if (nelem != 1 || pool_get_property(conf, pool_to_elem(conf, pools[0]), "pool.name", pvals[0]) != POC_STRING) { /* * Configuration is invalid. */ free(pools); pool_value_free(pvals[0]); (void) pool_conf_close(conf); pool_conf_free(conf); return (0); } free(pools); (void) pool_conf_close(conf); pool_conf_free(conf); (void) pool_value_get_string(pvals[0], &nm); if (pool_set_binding(nm, P_PID, pid) != PO_SUCCESS) { if (pool_error() != POE_SYSTEM) errno = EINVAL; retval = -1; } else { retval = 0; } pool_value_free(pvals[0]); return (retval); } /* * Changes the assigned project, task and resource pool of a stopped target * process. * * We may not have access to the project table if our target process is in * getprojbyname()'s execution path. Similarly, we may not be able to get user * information if the target process is in getpwnam()'s execution path. Thus we * give the caller the option of skipping these checks by providing a pointer to * a pre-validated project structure in proj (whose name matches project_name) * and taking responsibility for ensuring that the target process' owner is a * member of the target project. * * Callers of this function should always provide a pre-validated project * structure in proj unless they can be sure that the target process will never * be in setproject_proc()'s execution path. */ projid_t setproject_proc(const char *project_name, const char *user_name, int flags, pid_t pid, struct ps_prochandle *Pr, struct project *proj) { char pwdbuf[NSS_BUFLEN_PASSWD]; char prbuf[PROJECT_BUFSZ]; projid_t projid; struct passwd pwd; int i; int unknown = 0; int ret = 0; kva_t *kv_array; struct project local_proj; /* space to store proj if not provided */ const char *pool_name = NULL; if (project_name != NULL) { /* * Sanity checks. */ if (strcmp(project_name, "") == 0 || user_name == NULL) { errno = EINVAL; return (SETPROJ_ERR_TASK); } /* * If proj is NULL, acquire project information to ensure that * project_name is a valid project, and confirm that user_name * exists and is a member of the specified project. */ if (proj == NULL) { if ((proj = getprojbyname(project_name, &local_proj, prbuf, PROJECT_BUFSZ)) == NULL) { errno = ESRCH; return (SETPROJ_ERR_TASK); } if (getpwnam_r(user_name, &pwd, pwdbuf, NSS_BUFLEN_PASSWD) == NULL) { errno = ESRCH; return (SETPROJ_ERR_TASK); } /* * Root can join any project. */ if (pwd.pw_uid != (uid_t)0 && !inproj(user_name, project_name, prbuf, PROJECT_BUFSZ)) { errno = ESRCH; return (SETPROJ_ERR_TASK); } } projid = proj->pj_projid; } else { projid = getprojid(); } if ((kv_array = _str2kva(proj->pj_attr, KV_ASSIGN, KV_DELIMITER)) != NULL) { for (i = 0; i < kv_array->length; i++) { if (strcmp(kv_array->data[i].key, "project.pool") == 0) { pool_name = kv_array->data[i].value; } if (strcmp(kv_array->data[i].key, "task.final") == 0) { flags |= TASK_FINAL; } } } /* * Bind process to a pool only if pools are configured */ if (pools_enabled() == 1) { char *old_pool_name; /* * Attempt to bind to pool before calling * settaskid(). */ old_pool_name = pool_get_binding(pid); if (bind_to_pool(pool_name, pid, 0) != 0) { if (old_pool_name) free(old_pool_name); _kva_free(kv_array); return (SETPROJ_ERR_POOL); } if (pr_settaskid(Pr, projid, flags & TASK_MASK) == -1) { int saved_errno = errno; /* * Undo pool binding. */ (void) bind_to_pool(old_pool_name, pid, 1); if (old_pool_name) free(old_pool_name); _kva_free(kv_array); /* * Restore errno */ errno = saved_errno; return (SETPROJ_ERR_TASK); } if (old_pool_name) free(old_pool_name); } else { /* * Pools are not configured, so simply create new task. */ if (pr_settaskid(Pr, projid, flags & TASK_MASK) == -1) { _kva_free(kv_array); return (SETPROJ_ERR_TASK); } } if (project_name == NULL) { /* * In the case that we are starting a new task in the * current project, we are finished, since the current * resource controls will still apply. (Implicit behaviour: * a project must be entirely logged out before name * service changes will take effect.) */ _kva_free(kv_array); return (projid); } if (kv_array == NULL) return (0); for (i = 0; i < kv_array->length; i++) { /* * Providing a special, i.e. a non-resource control, key? Then * parse that key here and end with "continue;". */ /* * For generic bindings, the kernel performs the binding, as * these are resource controls advertised by kernel subsystems. */ /* * Check for known attribute name. */ errno = 0; if (rctl_walk(rctlwalkfunc, (void *)kv_array->data[i].key) == 0) continue; if (errno) { _kva_free(kv_array); return (SETPROJ_ERR_TASK); } ret = rctl_set(kv_array->data[i].key, kv_array->data[i].value, Pr, flags & TASK_PROJ_MASK); if (ret && unknown == 0) { /* * We only report the first failure. */ unknown = i + 1; } if (ret && ret != SETFAILED) { /* * We abort if we couldn't set a component, but if * it's merely that the system didn't recognize it, we * continue, as this could be a third party attribute. */ break; } } _kva_free(kv_array); return (unknown); } projid_t setproject(const char *project_name, const char *user_name, int flags) { return (setproject_proc(project_name, user_name, flags, P_MYID, NULL, NULL)); } priv_set_t * setproject_initpriv(void) { static priv_t taskpriv = PRIV_PROC_TASKID; static priv_t rctlpriv = PRIV_SYS_RESOURCE; static priv_t poolpriv = PRIV_SYS_RES_CONFIG; static priv_t schedpriv = PRIV_PROC_PRIOCNTL; int res; priv_set_t *nset; if (getzoneid() == GLOBAL_ZONEID) { res = __init_suid_priv(0, taskpriv, rctlpriv, poolpriv, schedpriv, (char *)NULL); } else { res = __init_suid_priv(0, taskpriv, rctlpriv, (char *)NULL); } if (res != 0) return (NULL); nset = priv_allocset(); if (nset != NULL) { priv_emptyset(nset); (void) priv_addset(nset, taskpriv); (void) priv_addset(nset, rctlpriv); /* * Only need these if we need to change pools, which can * only happen if the target is in the global zone. Rather * than checking the target's zone just check our own * (since if we're in a non-global zone we won't be able * to control processes in other zones). */ if (getzoneid() == GLOBAL_ZONEID) { (void) priv_addset(nset, poolpriv); (void) priv_addset(nset, schedpriv); } } return (nset); }