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