xref: /illumos-gate/usr/src/lib/libproject/common/setproject.c (revision 4de2612967d06c4fdbf524a62556a1e8118a006f)
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