Merge commit 'b1e7e97d3b60469b243b3b2e22c7d8cbd11c7c90'
[unleashed.git] / usr / src / cmd / pfexecd / pfexecd.c
blobae04a5fe4c44fdc81c4518f8b11b4b595cba2d22
1 /*
2 * CDDL HEADER START
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.
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.
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]
19 * CDDL HEADER END
21 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
22 * Copyright 2015, Joyent, Inc.
25 #define _POSIX_PTHREAD_SEMANTICS 1
27 #include <sys/param.h>
28 #include <sys/klpd.h>
29 #include <sys/syscall.h>
30 #include <sys/systeminfo.h>
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>
49 #include <auth_attr.h>
50 #include <exec_attr.h>
51 #include <prof_attr.h>
52 #include <user_attr.h>
54 static int doorfd = -1;
56 static size_t repsz, setsz;
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);
64 * Remove the isaexec path of an executable if we can't find the
65 * executable at the first attempt.
68 static regex_t regc;
69 static boolean_t cansplice = B_TRUE;
71 static void
72 init_isa_regex(void)
74 char *isalist;
75 size_t isalen = 255; /* wild guess */
76 size_t len;
77 long ret;
78 char *regexpr;
79 char *p;
82 * Extract the isalist(5) for userland from the kernel.
84 isalist = malloc(isalen);
85 do {
86 ret = sysinfo(SI_ISALIST, isalist, isalen);
87 if (ret == -1l) {
88 free(isalist);
89 return;
91 if (ret > isalen) {
92 isalen = ret;
93 isalist = realloc(isalist, isalen);
94 } else
95 break;
96 } while (isalist != NULL);
99 if (isalist == NULL)
100 return;
102 /* allocate room for the regex + (/())/[^/]*$ + needed \\. */
103 #define LEFT "(/("
104 #define RIGHT "))/[^/]*$"
106 regexpr = alloca(ret * 2 + sizeof (LEFT RIGHT));
107 (void) strcpy(regexpr, LEFT);
108 len = strlen(regexpr);
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;
132 free(isalist);
133 regexpr[len] = '\0';
134 (void) strcat(regexpr, RIGHT);
136 if (regcomp(&regc, regexpr, REG_EXTENDED) != 0)
137 return;
139 cansplice = B_TRUE;
142 #define NMATCH 2
144 static boolean_t
145 removeisapath(char *path)
147 regmatch_t match[NMATCH];
149 if (!cansplice || regexec(&regc, path, NMATCH, match, 0) != 0)
150 return (B_FALSE);
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.
158 if (match[1].rm_so == -1)
159 return (B_FALSE);
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);
165 return (B_TRUE);
168 static int
169 register_pfexec(int fd)
171 int ret = syscall(SYS_privsys, PRIVSYS_PFEXEC_REG, fd);
173 return (ret);
176 /* ARGSUSED */
177 static void
178 unregister_pfexec(int sig)
180 if (doorfd != -1)
181 (void) syscall(SYS_privsys, PRIVSYS_PFEXEC_UNREG, doorfd);
182 _exit(0);
185 static int
186 alldigits(const char *s)
188 int c;
190 if (*s == '\0')
191 return (0);
193 while ((c = *s++) != '\0') {
194 if (!isdigit(c)) {
195 return (0);
199 return (1);
202 static uid_t
203 get_uid(const char *v, boolean_t *ok, char *path)
205 struct passwd *pwd, pwdm;
206 char buf[1024];
208 if (getpwnam_r(v, &pwdm, buf, sizeof (buf), &pwd) == 0 && pwd != NULL)
209 return (pwd->pw_uid);
211 if (alldigits(v))
212 return (atoi(v));
214 *ok = B_FALSE;
215 syslog(LOG_ERR, "%s: %s: unknown username\n", path, v);
216 return ((uid_t)-1);
219 static uid_t
220 get_gid(const char *v, boolean_t *ok, char *path)
222 struct group *grp, grpm;
223 char buf[1024];
225 if (getgrnam_r(v, &grpm, buf, sizeof (buf), &grp) == 0 && grp != NULL)
226 return (grp->gr_gid);
228 if (alldigits(v))
229 return (atoi(v));
231 *ok = B_FALSE;
232 syslog(LOG_ERR, "%s: %s: unknown groupname\n", path, v);
233 return ((gid_t)-1);
236 static priv_set_t *
237 get_privset(const char *s, boolean_t *ok, char *path)
239 priv_set_t *res;
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;
246 return (res);
249 /*ARGSUSED*/
250 static int
251 ggp_callback(const char *prof, kva_t *attr, void *ctxt, void *vres)
253 priv_set_t *res = vres;
254 char *privs;
256 if (attr == NULL)
257 return (0);
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);
269 return (0);
273 * This routine exists on failure and returns NULL if no granted privileges
274 * are set.
276 static priv_set_t *
277 get_granted_privs(uid_t uid)
279 priv_set_t *res;
280 struct passwd *pwd, pwdm;
281 char buf[1024];
283 if (getpwuid_r(uid, &pwdm, buf, sizeof (buf), &pwd) != 0 || pwd == NULL)
284 return (NULL);
286 res = priv_allocset();
287 if (res == NULL)
288 return (NULL);
290 priv_emptyset(res);
292 (void) _enum_profs(pwd->pw_name, ggp_callback, NULL, res);
294 return (res);
297 static void
298 callback_forced_privs(pfexec_arg_t *pap)
300 execattr_t *exec;
301 char *value;
302 priv_set_t *fset;
303 void *res = alloca(setsz);
305 /* Empty set signifies no forced privileges. */
306 priv_emptyset(res);
308 exec = getexecprof("Forced Privilege", KV_COMMAND, pap->pfa_path,
309 GET_ONE);
311 if (exec == NULL && removeisapath(pap->pfa_path)) {
312 exec = getexecprof("Forced Privilege", KV_COMMAND,
313 pap->pfa_path, GET_ONE);
316 if (exec == NULL) {
317 (void) door_return(res, setsz, NULL, 0);
318 return;
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;
328 priv_copyset(fset, res);
329 priv_freeset(fset);
331 free_execattr(exec);
332 (void) door_return(res, setsz, NULL, 0);
335 static void
336 callback_user_privs(pfexec_arg_t *pap)
338 priv_set_t *gset, *wset;
339 uint32_t res;
341 wset = (priv_set_t *)&pap->pfa_buf;
342 gset = get_granted_privs(pap->pfa_uid);
344 res = priv_issubset(wset, gset);
345 priv_freeset(gset);
347 (void) door_return((char *)&res, sizeof (res), NULL, 0);
350 static void
351 callback_pfexec(pfexec_arg_t *pap)
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;
365 * Initialize the pfexec_reply_t to a sane state.
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;
379 uuid = pap->pfa_uid;
381 if (getpwuid_r(uuid, &pw, buf, sizeof (buf), &pwd) != 0 || pwd == NULL)
382 goto stdexec;
384 exec = getexecuser(pwd->pw_name, KV_COMMAND, path, GET_ONE);
386 if ((exec == NULL || exec->attr == NULL) && removeisapath(path)) {
387 free_execattr(exec);
388 exec = getexecuser(pwd->pw_name, KV_COMMAND, path, GET_ONE);
391 if (exec == NULL) {
392 res->pfr_allowed = B_FALSE;
393 goto ret;
396 if (exec->attr == NULL)
397 goto stdexec;
399 /* Found in execattr, so clearly we can use it */
400 res->pfr_allowed = B_TRUE;
402 uid = euid = (uid_t)-1;
403 gid = egid = (gid_t)-1;
404 lset = iset = NULL;
407 * If there's an error in parsing uid, gid, privs, then return
408 * failure.
410 if ((value = kva_match(exec->attr, EXECATTR_UID_KW)) != NULL)
411 euid = uid = get_uid(value, &res->pfr_allowed, path);
413 if ((value = kva_match(exec->attr, EXECATTR_GID_KW)) != NULL)
414 egid = gid = get_gid(value, &res->pfr_allowed, path);
416 if ((value = kva_match(exec->attr, EXECATTR_EUID_KW)) != NULL)
417 euid = get_uid(value, &res->pfr_allowed, path);
419 if ((value = kva_match(exec->attr, EXECATTR_EGID_KW)) != NULL)
420 egid = get_gid(value, &res->pfr_allowed, path);
422 if ((value = kva_match(exec->attr, EXECATTR_LPRIV_KW)) != NULL)
423 lset = get_privset(value, &res->pfr_allowed, path);
425 if ((value = kva_match(exec->attr, EXECATTR_IPRIV_KW)) != NULL)
426 iset = get_privset(value, &res->pfr_allowed, path);
429 * Remove LD_* variables in the kernel when the runtime linker might
430 * use them later on because the uids are equal.
432 res->pfr_scrubenv = (uid != (uid_t)-1 && euid == uid) ||
433 (gid != (gid_t)-1 && egid == gid) || iset != NULL;
435 res->pfr_euid = euid;
436 res->pfr_ruid = uid;
437 res->pfr_egid = egid;
438 res->pfr_rgid = gid;
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);
448 if (lset != NULL) {
449 res->pfr_loff = mysz;
450 priv_copyset(lset, PFEXEC_REPLY_LPRIV(res));
451 mysz += setsz;
452 priv_freeset(lset);
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;
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;
466 stdexec:
467 free_execattr(exec);
469 res->pfr_scrubenv = B_FALSE;
470 res->pfr_setcred = B_FALSE;
471 res->pfr_allowed = B_TRUE;
473 (void) door_return((char *)res, mysz, NULL, 0);
476 /* ARGSUSED */
477 static void
478 callback(void *cookie, char *argp, size_t asz, door_desc_t *dp, uint_t ndesc)
480 /* LINTED ALIGNMENT */
481 pfexec_arg_t *pap = (pfexec_arg_t *)argp;
483 if (asz < sizeof (pfexec_arg_t) || pap->pfa_vers != PFEXEC_ARG_VERS) {
484 (void) door_return(NULL, 0, NULL, 0);
485 return;
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;
504 * If the door_return(ptr, size, NULL, 0) fails, make sure we
505 * don't lose server threads.
507 (void) door_return(NULL, 0, NULL, 0);
511 main(void)
513 const priv_impl_info_t *info;
515 (void) signal(SIGINT, unregister_pfexec);
516 (void) signal(SIGQUIT, unregister_pfexec);
517 (void) signal(SIGTERM, unregister_pfexec);
518 (void) signal(SIGHUP, unregister_pfexec);
520 info = getprivimplinfo();
521 if (info == NULL)
522 exit(1);
524 if (fork() > 0)
525 _exit(0);
527 openlog("pfexecd", LOG_PID, LOG_DAEMON);
528 setsz = info->priv_setsize * sizeof (priv_chunk_t);
529 repsz = 2 * setsz + sizeof (pfexec_reply_t);
531 init_isa_regex();
533 doorfd = door_create(callback, NULL, DOOR_REFUSE_DESC);
535 if (doorfd == -1 || register_pfexec(doorfd) != 0) {
536 perror("doorfd");
537 exit(1);
540 /* LINTED CONSTCOND */
541 while (1)
542 (void) sigpause(SIGINT);
544 return (0);