xref: /illumos-gate/usr/src/cmd/pfexecd/pfexecd.c (revision dd72704bd9e794056c558153663c739e2012d721)
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  * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
22  * Copyright 2015, Joyent, Inc.
23  */
24 
25 #define	_POSIX_PTHREAD_SEMANTICS 1
26 
27 #include <sys/param.h>
28 #include <sys/klpd.h>
29 #include <sys/syscall.h>
30 #include <sys/systeminfo.h>
31 
32 #include <alloca.h>
33 #include <ctype.h>
34 #include <deflt.h>
35 #include <door.h>
36 #include <errno.h>
37 #include <grp.h>
38 #include <priv.h>
39 #include <pwd.h>
40 #include <regex.h>
41 #include <secdb.h>
42 #include <signal.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <syslog.h>
47 #include <unistd.h>
48 
49 #include <auth_attr.h>
50 #include <exec_attr.h>
51 #include <prof_attr.h>
52 #include <user_attr.h>
53 
54 static int doorfd = -1;
55 
56 static size_t repsz, setsz;
57 
58 static uid_t get_uid(const char *, boolean_t *, char *);
59 static gid_t get_gid(const char *, boolean_t *, char *);
60 static priv_set_t *get_privset(const char *, boolean_t *, char *);
61 static priv_set_t *get_granted_privs(uid_t);
62 
63 /*
64  * Remove the isaexec path of an executable if we can't find the
65  * executable at the first attempt.
66  */
67 
68 static regex_t regc;
69 static boolean_t cansplice = B_TRUE;
70 
71 static void
72 init_isa_regex(void)
73 {
74 	char *isalist;
75 	size_t isalen = 255;		/* wild guess */
76 	size_t len;
77 	long ret;
78 	char *regexpr;
79 	char *p;
80 
81 	/*
82 	 * Extract the isalist(7) for userland from the kernel.
83 	 */
84 	isalist = malloc(isalen);
85 	do {
86 		ret = sysinfo(SI_ISALIST, isalist, isalen);
87 		if (ret == -1l) {
88 			free(isalist);
89 			return;
90 		}
91 		if (ret > isalen) {
92 			isalen = ret;
93 			isalist = realloc(isalist, isalen);
94 		} else
95 			break;
96 	} while (isalist != NULL);
97 
98 
99 	if (isalist == NULL)
100 		return;
101 
102 	/* allocate room for the regex + (/())/[^/]*$ + needed \\. */
103 #define	LEFT	"(/("
104 #define	RIGHT	"))/[^/]*$"
105 
106 	regexpr = alloca(ret * 2 + sizeof (LEFT RIGHT));
107 	(void) strcpy(regexpr, LEFT);
108 	len = strlen(regexpr);
109 
110 	for (p = isalist; *p; p++) {
111 		switch (*p) {
112 		case '+':
113 		case '|':
114 		case '*':
115 		case '[':
116 		case ']':
117 		case '{':
118 		case '}':
119 		case '\\':
120 			regexpr[len++] = '\\';
121 			/* FALLTHROUGH */
122 		default:
123 			regexpr[len++] = *p;
124 			break;
125 		case ' ':
126 		case '\t':
127 			regexpr[len++] = '|';
128 			break;
129 		}
130 	}
131 
132 	free(isalist);
133 	regexpr[len] = '\0';
134 	(void) strcat(regexpr, RIGHT);
135 
136 	if (regcomp(&regc, regexpr, REG_EXTENDED) != 0)
137 		return;
138 
139 	cansplice = B_TRUE;
140 }
141 
142 #define	NMATCH	2
143 
144 static boolean_t
145 removeisapath(char *path)
146 {
147 	regmatch_t match[NMATCH];
148 
149 	if (!cansplice || regexec(&regc, path, NMATCH, match, 0) != 0)
150 		return (B_FALSE);
151 
152 	/*
153 	 * The first match includes the whole matched expression including the
154 	 * end of the string.  The second match includes the "/" + "isa" and
155 	 * that is the part we need to remove.
156 	 */
157 
158 	if (match[1].rm_so == -1)
159 		return (B_FALSE);
160 
161 	/* match[0].rm_eo == strlen(path) */
162 	(void) memmove(path + match[1].rm_so, path + match[1].rm_eo,
163 	    match[0].rm_eo - match[1].rm_eo + 1);
164 
165 	return (B_TRUE);
166 }
167 
168 static int
169 register_pfexec(int fd)
170 {
171 	int ret = syscall(SYS_privsys, PRIVSYS_PFEXEC_REG, fd);
172 
173 	return (ret);
174 }
175 
176 /* ARGSUSED */
177 static void
178 unregister_pfexec(int sig)
179 {
180 	if (doorfd != -1)
181 		(void) syscall(SYS_privsys, PRIVSYS_PFEXEC_UNREG, doorfd);
182 	_exit(0);
183 }
184 
185 static int
186 alldigits(const char *s)
187 {
188 	int c;
189 
190 	if (*s == '\0')
191 		return (0);
192 
193 	while ((c = *s++) != '\0') {
194 		if (!isdigit(c)) {
195 			return (0);
196 		}
197 	}
198 
199 	return (1);
200 }
201 
202 static uid_t
203 get_uid(const char *v, boolean_t *ok, char *path)
204 {
205 	struct passwd *pwd, pwdm;
206 	char buf[1024];
207 
208 	if (getpwnam_r(v, &pwdm, buf, sizeof (buf), &pwd) == 0 && pwd != NULL)
209 		return (pwd->pw_uid);
210 
211 	if (alldigits(v))
212 		return (atoi(v));
213 
214 	*ok = B_FALSE;
215 	syslog(LOG_ERR, "%s: %s: unknown username\n", path, v);
216 	return ((uid_t)-1);
217 }
218 
219 static uid_t
220 get_gid(const char *v, boolean_t *ok, char *path)
221 {
222 	struct group *grp, grpm;
223 	char buf[1024];
224 
225 	if (getgrnam_r(v, &grpm, buf, sizeof (buf), &grp) == 0 && grp != NULL)
226 		return (grp->gr_gid);
227 
228 	if (alldigits(v))
229 		return (atoi(v));
230 
231 	*ok = B_FALSE;
232 	syslog(LOG_ERR, "%s: %s: unknown groupname\n", path, v);
233 	return ((gid_t)-1);
234 }
235 
236 static priv_set_t *
237 get_privset(const char *s, boolean_t *ok, char *path)
238 {
239 	priv_set_t *res;
240 
241 	if ((res = priv_str_to_set(s, ",", NULL)) == NULL) {
242 		syslog(LOG_ERR, "%s: %s: bad privilege set\n", path, s);
243 		if (ok != NULL)
244 			*ok = B_FALSE;
245 	}
246 	return (res);
247 }
248 
249 /*ARGSUSED*/
250 static int
251 ggp_callback(const char *prof, kva_t *attr, void *ctxt, void *vres)
252 {
253 	priv_set_t *res = vres;
254 	char *privs;
255 
256 	if (attr == NULL)
257 		return (0);
258 
259 	/* get privs from this profile */
260 	privs = kva_match(attr, PROFATTR_PRIVS_KW);
261 	if (privs != NULL) {
262 		priv_set_t *tmp = priv_str_to_set(privs, ",", NULL);
263 		if (tmp != NULL) {
264 			priv_union(tmp, res);
265 			priv_freeset(tmp);
266 		}
267 	}
268 
269 	return (0);
270 }
271 
272 /*
273  * This routine exists on failure and returns NULL if no granted privileges
274  * are set.
275  */
276 static priv_set_t *
277 get_granted_privs(uid_t uid)
278 {
279 	priv_set_t *res;
280 	struct passwd *pwd, pwdm;
281 	char buf[1024];
282 
283 	if (getpwuid_r(uid, &pwdm, buf, sizeof (buf), &pwd) != 0 || pwd == NULL)
284 		return (NULL);
285 
286 	res = priv_allocset();
287 	if (res == NULL)
288 		return (NULL);
289 
290 	priv_emptyset(res);
291 
292 	(void) _enum_profs(pwd->pw_name, ggp_callback, NULL, res);
293 
294 	return (res);
295 }
296 
297 static void
298 callback_forced_privs(pfexec_arg_t *pap)
299 {
300 	execattr_t *exec;
301 	char *value;
302 	priv_set_t *fset;
303 	void *res = alloca(setsz);
304 
305 	/* Empty set signifies no forced privileges. */
306 	priv_emptyset(res);
307 
308 	exec = getexecprof("Forced Privilege", KV_COMMAND, pap->pfa_path,
309 	    GET_ONE);
310 
311 	if (exec == NULL && removeisapath(pap->pfa_path)) {
312 		exec = getexecprof("Forced Privilege", KV_COMMAND,
313 		    pap->pfa_path, GET_ONE);
314 	}
315 
316 	if (exec == NULL) {
317 		(void) door_return(res, setsz, NULL, 0);
318 		return;
319 	}
320 
321 	if ((value = kva_match(exec->attr, EXECATTR_IPRIV_KW)) == NULL ||
322 	    (fset = get_privset(value, NULL, pap->pfa_path)) == NULL) {
323 		free_execattr(exec);
324 		(void) door_return(res, setsz, NULL, 0);
325 		return;
326 	}
327 
328 	priv_copyset(fset, res);
329 	priv_freeset(fset);
330 
331 	free_execattr(exec);
332 	(void) door_return(res, setsz, NULL, 0);
333 }
334 
335 static void
336 callback_user_privs(pfexec_arg_t *pap)
337 {
338 	priv_set_t *gset, *wset;
339 	uint32_t res;
340 
341 	wset = (priv_set_t *)&pap->pfa_buf;
342 	gset = get_granted_privs(pap->pfa_uid);
343 
344 	res = priv_issubset(wset, gset);
345 	priv_freeset(gset);
346 
347 	(void) door_return((char *)&res, sizeof (res), NULL, 0);
348 }
349 
350 static void
351 callback_pfexec(pfexec_arg_t *pap)
352 {
353 	pfexec_reply_t *res = alloca(repsz);
354 	uid_t uid, euid, uuid;
355 	gid_t gid, egid;
356 	struct passwd pw, *pwd;
357 	char buf[1024];
358 	execattr_t *exec = NULL;
359 	char *value;
360 	priv_set_t *lset, *iset;
361 	size_t mysz = repsz - 2 * setsz;
362 	char *path = pap->pfa_path;
363 
364 	/*
365 	 * Initialize the pfexec_reply_t to a sane state.
366 	 */
367 	res->pfr_vers = pap->pfa_vers;
368 	res->pfr_len = 0;
369 	res->pfr_ruid = PFEXEC_NOTSET;
370 	res->pfr_euid = PFEXEC_NOTSET;
371 	res->pfr_rgid = PFEXEC_NOTSET;
372 	res->pfr_egid = PFEXEC_NOTSET;
373 	res->pfr_setcred = B_FALSE;
374 	res->pfr_scrubenv = B_TRUE;
375 	res->pfr_allowed = B_FALSE;
376 	res->pfr_ioff = 0;
377 	res->pfr_loff = 0;
378 
379 	uuid = pap->pfa_uid;
380 
381 	if (getpwuid_r(uuid, &pw, buf, sizeof (buf), &pwd) != 0 || pwd == NULL)
382 		goto stdexec;
383 
384 	exec = getexecuser(pwd->pw_name, KV_COMMAND, path, GET_ONE);
385 
386 	if ((exec == NULL || exec->attr == NULL) && removeisapath(path)) {
387 		free_execattr(exec);
388 		exec = getexecuser(pwd->pw_name, KV_COMMAND, path, GET_ONE);
389 	}
390 
391 	if (exec == NULL) {
392 		res->pfr_allowed = B_FALSE;
393 		goto ret;
394 	}
395 
396 	if (exec->attr == NULL)
397 		goto stdexec;
398 
399 	/* Found in execattr, so clearly we can use it */
400 	res->pfr_allowed = B_TRUE;
401 
402 	uid = euid = (uid_t)-1;
403 	gid = egid = (gid_t)-1;
404 	lset = iset = NULL;
405 
406 	/*
407 	 * If there's an error in parsing uid, gid, privs, then return
408 	 * failure.
409 	 */
410 	if ((value = kva_match(exec->attr, EXECATTR_UID_KW)) != NULL)
411 		euid = uid = get_uid(value, &res->pfr_allowed, path);
412 
413 	if ((value = kva_match(exec->attr, EXECATTR_GID_KW)) != NULL)
414 		egid = gid = get_gid(value, &res->pfr_allowed, path);
415 
416 	if ((value = kva_match(exec->attr, EXECATTR_EUID_KW)) != NULL)
417 		euid = get_uid(value, &res->pfr_allowed, path);
418 
419 	if ((value = kva_match(exec->attr, EXECATTR_EGID_KW)) != NULL)
420 		egid = get_gid(value, &res->pfr_allowed, path);
421 
422 	if ((value = kva_match(exec->attr, EXECATTR_LPRIV_KW)) != NULL)
423 		lset = get_privset(value, &res->pfr_allowed, path);
424 
425 	if ((value = kva_match(exec->attr, EXECATTR_IPRIV_KW)) != NULL)
426 		iset = get_privset(value, &res->pfr_allowed, path);
427 
428 	/*
429 	 * Remove LD_* variables in the kernel when the runtime linker might
430 	 * use them later on because the uids are equal.
431 	 */
432 	res->pfr_scrubenv = (uid != (uid_t)-1 && euid == uid) ||
433 	    (gid != (gid_t)-1 && egid == gid) || iset != NULL;
434 
435 	res->pfr_euid = euid;
436 	res->pfr_ruid = uid;
437 	res->pfr_egid = egid;
438 	res->pfr_rgid = gid;
439 
440 	/* Now add the privilege sets */
441 	res->pfr_ioff = res->pfr_loff = 0;
442 	if (iset != NULL) {
443 		res->pfr_ioff = mysz;
444 		priv_copyset(iset, PFEXEC_REPLY_IPRIV(res));
445 		mysz += setsz;
446 		priv_freeset(iset);
447 	}
448 	if (lset != NULL) {
449 		res->pfr_loff = mysz;
450 		priv_copyset(lset, PFEXEC_REPLY_LPRIV(res));
451 		mysz += setsz;
452 		priv_freeset(lset);
453 	}
454 
455 	res->pfr_setcred = uid != (uid_t)-1 || euid != (uid_t)-1 ||
456 	    egid != (gid_t)-1 || gid != (gid_t)-1 || iset != NULL ||
457 	    lset != NULL;
458 
459 	/* If the real uid changes, we stop running under a profile shell */
460 	res->pfr_clearflag = uid != (uid_t)-1 && uid != uuid;
461 	free_execattr(exec);
462 ret:
463 	(void) door_return((char *)res, mysz, NULL, 0);
464 	return;
465 
466 stdexec:
467 	free_execattr(exec);
468 
469 	res->pfr_scrubenv = B_FALSE;
470 	res->pfr_setcred = B_FALSE;
471 	res->pfr_allowed = B_TRUE;
472 
473 	(void) door_return((char *)res, mysz, NULL, 0);
474 }
475 
476 /* ARGSUSED */
477 static void
478 callback(void *cookie, char *argp, size_t asz, door_desc_t *dp, uint_t ndesc)
479 {
480 	/* LINTED ALIGNMENT */
481 	pfexec_arg_t *pap = (pfexec_arg_t *)argp;
482 
483 	if (asz < sizeof (pfexec_arg_t) || pap->pfa_vers != PFEXEC_ARG_VERS) {
484 		(void) door_return(NULL, 0, NULL, 0);
485 		return;
486 	}
487 
488 	switch (pap->pfa_call) {
489 	case PFEXEC_EXEC_ATTRS:
490 		callback_pfexec(pap);
491 		break;
492 	case PFEXEC_FORCED_PRIVS:
493 		callback_forced_privs(pap);
494 		break;
495 	case PFEXEC_USER_PRIVS:
496 		callback_user_privs(pap);
497 		break;
498 	default:
499 		syslog(LOG_ERR, "Bad Call: %d\n", pap->pfa_call);
500 		break;
501 	}
502 
503 	/*
504 	 * If the door_return(ptr, size, NULL, 0) fails, make sure we
505 	 * don't lose server threads.
506 	 */
507 	(void) door_return(NULL, 0, NULL, 0);
508 }
509 
510 int
511 main(void)
512 {
513 	const priv_impl_info_t *info;
514 
515 	(void) signal(SIGINT, unregister_pfexec);
516 	(void) signal(SIGQUIT, unregister_pfexec);
517 	(void) signal(SIGTERM, unregister_pfexec);
518 	(void) signal(SIGHUP, unregister_pfexec);
519 
520 	info = getprivimplinfo();
521 	if (info == NULL)
522 		exit(1);
523 
524 	if (fork() > 0)
525 		_exit(0);
526 
527 	openlog("pfexecd", LOG_PID, LOG_DAEMON);
528 	setsz = info->priv_setsize * sizeof (priv_chunk_t);
529 	repsz = 2 * setsz + sizeof (pfexec_reply_t);
530 
531 	init_isa_regex();
532 
533 	doorfd = door_create(callback, NULL, DOOR_REFUSE_DESC);
534 
535 	if (doorfd == -1 || register_pfexec(doorfd) != 0) {
536 		perror("doorfd");
537 		exit(1);
538 	}
539 
540 	/* LINTED CONSTCOND */
541 	while (1)
542 		(void) sigpause(SIGINT);
543 
544 	return (0);
545 }
546