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