xref: /illumos-gate/usr/src/cmd/pfexecd/pfexecd.c (revision 134a1f4e)
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  *
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(5) 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 		default:
122 			regexpr[len++] = *p;
123 			break;
124 		case ' ':
125 		case '\t':
126 			regexpr[len++] = '|';
127 			break;
128 		}
129 	}
130 
131 	free(isalist);
132 	regexpr[len] = '\0';
133 	(void) strcat(regexpr, RIGHT);
134 
135 	if (regcomp(&regc, regexpr, REG_EXTENDED) != 0)
136 		return;
137 
138 	cansplice = B_TRUE;
139 }
140 
141 #define	NMATCH	2
142 
143 static boolean_t
144 removeisapath(char *path)
145 {
146 	regmatch_t match[NMATCH];
147 
148 	if (!cansplice || regexec(&regc, path, NMATCH, match, 0) != 0)
149 		return (B_FALSE);
150 
151 	/*
152 	 * The first match includes the whole matched expression including the
153 	 * end of the string.  The second match includes the "/" + "isa" and
154 	 * that is the part we need to remove.
155 	 */
156 
157 	if (match[1].rm_so == -1)
158 		return (B_FALSE);
159 
160 	/* match[0].rm_eo == strlen(path) */
161 	(void) memmove(path + match[1].rm_so, path + match[1].rm_eo,
162 	    match[0].rm_eo - match[1].rm_eo + 1);
163 
164 	return (B_TRUE);
165 }
166 
167 static int
168 register_pfexec(int fd)
169 {
170 	int ret = syscall(SYS_privsys, PRIVSYS_PFEXEC_REG, fd);
171 
172 	return (ret);
173 }
174 
175 /* ARGSUSED */
176 static void
177 unregister_pfexec(int sig)
178 {
179 	if (doorfd != -1)
180 		(void) syscall(SYS_privsys, PRIVSYS_PFEXEC_UNREG, doorfd);
181 	_exit(0);
182 }
183 
184 static int
185 alldigits(const char *s)
186 {
187 	int c;
188 
189 	if (*s == '\0')
190 		return (0);
191 
192 	while ((c = *s++) != '\0') {
193 		if (!isdigit(c)) {
194 			return (0);
195 		}
196 	}
197 
198 	return (1);
199 }
200 
201 static uid_t
202 get_uid(const char *v, boolean_t *ok, char *path)
203 {
204 	struct passwd *pwd, pwdm;
205 	char buf[1024];
206 
207 	if (getpwnam_r(v, &pwdm, buf, sizeof (buf), &pwd) == 0 && pwd != NULL)
208 		return (pwd->pw_uid);
209 
210 	if (alldigits(v))
211 		return (atoi(v));
212 
213 	*ok = B_FALSE;
214 	syslog(LOG_ERR, "%s: %s: unknown username\n", path, v);
215 	return ((uid_t)-1);
216 }
217 
218 static uid_t
219 get_gid(const char *v, boolean_t *ok, char *path)
220 {
221 	struct group *grp, grpm;
222 	char buf[1024];
223 
224 	if (getgrnam_r(v, &grpm, buf, sizeof (buf), &grp) == 0 && grp != NULL)
225 		return (grp->gr_gid);
226 
227 	if (alldigits(v))
228 		return (atoi(v));
229 
230 	*ok = B_FALSE;
231 	syslog(LOG_ERR, "%s: %s: unknown groupname\n", path, v);
232 	return ((gid_t)-1);
233 }
234 
235 static priv_set_t *
236 get_privset(const char *s, boolean_t *ok, char *path)
237 {
238 	priv_set_t *res;
239 
240 	if ((res = priv_str_to_set(s, ",", NULL)) == NULL) {
241 		syslog(LOG_ERR, "%s: %s: bad privilege set\n", path, s);
242 		if (ok != NULL)
243 			*ok = B_FALSE;
244 	}
245 	return (res);
246 }
247 
248 /*ARGSUSED*/
249 static int
250 ggp_callback(const char *prof, kva_t *attr, void *ctxt, void *vres)
251 {
252 	priv_set_t *res = vres;
253 	char *privs;
254 
255 	if (attr == NULL)
256 		return (0);
257 
258 	/* get privs from this profile */
259 	privs = kva_match(attr, PROFATTR_PRIVS_KW);
260 	if (privs != NULL) {
261 		priv_set_t *tmp = priv_str_to_set(privs, ",", NULL);
262 		if (tmp != NULL) {
263 			priv_union(tmp, res);
264 			priv_freeset(tmp);
265 		}
266 	}
267 
268 	return (0);
269 }
270 
271 /*
272  * This routine exists on failure and returns NULL if no granted privileges
273  * are set.
274  */
275 static priv_set_t *
276 get_granted_privs(uid_t uid)
277 {
278 	priv_set_t *res;
279 	struct passwd *pwd, pwdm;
280 	char buf[1024];
281 
282 	if (getpwuid_r(uid, &pwdm, buf, sizeof (buf), &pwd) != 0 || pwd == NULL)
283 		return (NULL);
284 
285 	res = priv_allocset();
286 	if (res == NULL)
287 		return (NULL);
288 
289 	priv_emptyset(res);
290 
291 	(void) _enum_profs(pwd->pw_name, ggp_callback, NULL, res);
292 
293 	return (res);
294 }
295 
296 static void
297 callback_forced_privs(pfexec_arg_t *pap)
298 {
299 	execattr_t *exec;
300 	char *value;
301 	priv_set_t *fset;
302 	void *res = alloca(setsz);
303 
304 	/* Empty set signifies no forced privileges. */
305 	priv_emptyset(res);
306 
307 	exec = getexecprof("Forced Privilege", KV_COMMAND, pap->pfa_path,
308 	    GET_ONE);
309 
310 	if (exec == NULL && removeisapath(pap->pfa_path)) {
311 		exec = getexecprof("Forced Privilege", KV_COMMAND,
312 		    pap->pfa_path, GET_ONE);
313 	}
314 
315 	if (exec == NULL) {
316 		(void) door_return(res, setsz, NULL, 0);
317 		return;
318 	}
319 
320 	if ((value = kva_match(exec->attr, EXECATTR_IPRIV_KW)) == NULL ||
321 	    (fset = get_privset(value, NULL, pap->pfa_path)) == NULL) {
322 		free_execattr(exec);
323 		(void) door_return(res, setsz, NULL, 0);
324 		return;
325 	}
326 
327 	priv_copyset(fset, res);
328 	priv_freeset(fset);
329 
330 	free_execattr(exec);
331 	(void) door_return(res, setsz, NULL, 0);
332 }
333 
334 static void
335 callback_user_privs(pfexec_arg_t *pap)
336 {
337 	priv_set_t *gset, *wset;
338 	uint32_t res;
339 
340 	wset = (priv_set_t *)&pap->pfa_buf;
341 	gset = get_granted_privs(pap->pfa_uid);
342 
343 	res = priv_issubset(wset, gset);
344 	priv_freeset(gset);
345 
346 	(void) door_return((char *)&res, sizeof (res), NULL, 0);
347 }
348 
349 static void
350 callback_pfexec(pfexec_arg_t *pap)
351 {
352 	pfexec_reply_t *res = alloca(repsz);
353 	uid_t uid, euid, uuid;
354 	gid_t gid, egid;
355 	struct passwd pw, *pwd;
356 	char buf[1024];
357 	execattr_t *exec;
358 	char *value;
359 	priv_set_t *lset, *iset;
360 	size_t mysz = repsz - 2 * setsz;
361 	char *path = pap->pfa_path;
362 
363 	uuid = pap->pfa_uid;
364 
365 	if (getpwuid_r(uuid, &pw, buf, sizeof (buf), &pwd) != 0 || pwd == NULL)
366 		goto stdexec;
367 
368 	exec = getexecuser(pwd->pw_name, KV_COMMAND, path, GET_ONE);
369 
370 	if (exec == NULL && removeisapath(path))
371 		exec = getexecuser(pwd->pw_name, KV_COMMAND, path, GET_ONE);
372 
373 	if (exec == NULL) {
374 		res->pfr_allowed = B_FALSE;
375 		goto ret;
376 	}
377 
378 	if (exec->attr == NULL)
379 		goto stdexec;
380 
381 	/* Found in execattr, so clearly we can use it */
382 	res->pfr_allowed = B_TRUE;
383 
384 	uid = euid = (uid_t)-1;
385 	gid = egid = (gid_t)-1;
386 	lset = iset = NULL;
387 
388 	/*
389 	 * If there's an error in parsing uid, gid, privs, then return
390 	 * failure.
391 	 */
392 	if ((value = kva_match(exec->attr, EXECATTR_UID_KW)) != NULL)
393 		euid = uid = get_uid(value, &res->pfr_allowed, path);
394 
395 	if ((value = kva_match(exec->attr, EXECATTR_GID_KW)) != NULL)
396 		egid = gid = get_gid(value, &res->pfr_allowed, path);
397 
398 	if ((value = kva_match(exec->attr, EXECATTR_EUID_KW)) != NULL)
399 		euid = get_uid(value, &res->pfr_allowed, path);
400 
401 	if ((value = kva_match(exec->attr, EXECATTR_EGID_KW)) != NULL)
402 		egid = get_gid(value, &res->pfr_allowed, path);
403 
404 	if ((value = kva_match(exec->attr, EXECATTR_LPRIV_KW)) != NULL)
405 		lset = get_privset(value, &res->pfr_allowed, path);
406 
407 	if ((value = kva_match(exec->attr, EXECATTR_IPRIV_KW)) != NULL)
408 		iset = get_privset(value, &res->pfr_allowed, path);
409 
410 	/*
411 	 * Remove LD_* variables in the kernel when the runtime linker might
412 	 * use them later on because the uids are equal.
413 	 */
414 	res->pfr_scrubenv = (uid != (uid_t)-1 && euid == uid) ||
415 	    (gid != (gid_t)-1 && egid == gid) || iset != NULL;
416 
417 	res->pfr_euid = euid;
418 	res->pfr_ruid = uid;
419 	res->pfr_egid = egid;
420 	res->pfr_rgid = gid;
421 
422 	/* Now add the privilege sets */
423 	res->pfr_ioff = res->pfr_loff = 0;
424 	if (iset != NULL) {
425 		res->pfr_ioff = mysz;
426 		priv_copyset(iset, PFEXEC_REPLY_IPRIV(res));
427 		mysz += setsz;
428 		priv_freeset(iset);
429 	}
430 	if (lset != NULL) {
431 		res->pfr_loff = mysz;
432 		priv_copyset(lset, PFEXEC_REPLY_LPRIV(res));
433 		mysz += setsz;
434 		priv_freeset(lset);
435 	}
436 
437 	res->pfr_setcred = uid != (uid_t)-1 || euid != (uid_t)-1 ||
438 	    egid != (gid_t)-1 || gid != (gid_t)-1 || iset != NULL ||
439 	    lset != NULL;
440 
441 	/* If the real uid changes, we stop running under a profile shell */
442 	res->pfr_clearflag = uid != (uid_t)-1 && uid != uuid;
443 	free_execattr(exec);
444 ret:
445 	(void) door_return((char *)res, mysz, NULL, 0);
446 	return;
447 
448 stdexec:
449 	res->pfr_scrubenv = B_FALSE;
450 	res->pfr_setcred = B_FALSE;
451 	res->pfr_allowed = B_TRUE;
452 
453 	(void) door_return((char *)res, mysz, NULL, 0);
454 }
455 
456 /* ARGSUSED */
457 static void
458 callback(void *cookie, char *argp, size_t asz, door_desc_t *dp, uint_t ndesc)
459 {
460 	/* LINTED ALIGNMENT */
461 	pfexec_arg_t *pap = (pfexec_arg_t *)argp;
462 
463 	if (asz < sizeof (pfexec_arg_t) || pap->pfa_vers != PFEXEC_ARG_VERS) {
464 		(void) door_return(NULL, 0, NULL, 0);
465 		return;
466 	}
467 
468 	switch (pap->pfa_call) {
469 	case PFEXEC_EXEC_ATTRS:
470 		callback_pfexec(pap);
471 		break;
472 	case PFEXEC_FORCED_PRIVS:
473 		callback_forced_privs(pap);
474 		break;
475 	case PFEXEC_USER_PRIVS:
476 		callback_user_privs(pap);
477 		break;
478 	default:
479 		syslog(LOG_ERR, "Bad Call: %d\n", pap->pfa_call);
480 		break;
481 	}
482 
483 	/*
484 	 * If the door_return(ptr, size, NULL, 0) fails, make sure we
485 	 * don't lose server threads.
486 	 */
487 	(void) door_return(NULL, 0, NULL, 0);
488 }
489 
490 int
491 main(void)
492 {
493 	const priv_impl_info_t *info;
494 
495 	(void) signal(SIGINT, unregister_pfexec);
496 	(void) signal(SIGQUIT, unregister_pfexec);
497 	(void) signal(SIGTERM, unregister_pfexec);
498 	(void) signal(SIGHUP, unregister_pfexec);
499 
500 	info = getprivimplinfo();
501 	if (info == NULL)
502 		exit(1);
503 
504 	if (fork() > 0)
505 		_exit(0);
506 
507 	openlog("pfexecd", LOG_PID, LOG_DAEMON);
508 	setsz = info->priv_setsize * sizeof (priv_chunk_t);
509 	repsz = 2 * setsz + sizeof (pfexec_reply_t);
510 
511 	init_isa_regex();
512 
513 	doorfd = door_create(callback, NULL, DOOR_REFUSE_DESC);
514 
515 	if (doorfd == -1 || register_pfexec(doorfd) != 0) {
516 		perror("doorfd");
517 		exit(1);
518 	}
519 
520 	/* LINTED CONSTCOND */
521 	while (1)
522 		(void) sigpause(SIGINT);
523 
524 	return (0);
525 }
526