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