dmake: do not set MAKEFLAGS=k
[unleashed/tickless.git] / usr / src / cmd / pfexecd / pfexecd.c
blob1d25c09188beac38fd5b1ee6a8f1749cd99e831c
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 default:
122 regexpr[len++] = *p;
123 break;
124 case ' ':
125 case '\t':
126 regexpr[len++] = '|';
127 break;
131 free(isalist);
132 regexpr[len] = '\0';
133 (void) strcat(regexpr, RIGHT);
135 if (regcomp(&regc, regexpr, REG_EXTENDED) != 0)
136 return;
138 cansplice = B_TRUE;
141 #define NMATCH 2
143 static boolean_t
144 removeisapath(char *path)
146 regmatch_t match[NMATCH];
148 if (!cansplice || regexec(&regc, path, NMATCH, match, 0) != 0)
149 return (B_FALSE);
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.
157 if (match[1].rm_so == -1)
158 return (B_FALSE);
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);
164 return (B_TRUE);
167 static int
168 register_pfexec(int fd)
170 int ret = syscall(SYS_privsys, PRIVSYS_PFEXEC_REG, fd);
172 return (ret);
175 /* ARGSUSED */
176 static void
177 unregister_pfexec(int sig)
179 if (doorfd != -1)
180 (void) syscall(SYS_privsys, PRIVSYS_PFEXEC_UNREG, doorfd);
181 _exit(0);
184 static int
185 alldigits(const char *s)
187 int c;
189 if (*s == '\0')
190 return (0);
192 while ((c = *s++) != '\0') {
193 if (!isdigit(c)) {
194 return (0);
198 return (1);
201 static uid_t
202 get_uid(const char *v, boolean_t *ok, char *path)
204 struct passwd *pwd, pwdm;
205 char buf[1024];
207 if (getpwnam_r(v, &pwdm, buf, sizeof (buf), &pwd) == 0 && pwd != NULL)
208 return (pwd->pw_uid);
210 if (alldigits(v))
211 return (atoi(v));
213 *ok = B_FALSE;
214 syslog(LOG_ERR, "%s: %s: unknown username\n", path, v);
215 return ((uid_t)-1);
218 static uid_t
219 get_gid(const char *v, boolean_t *ok, char *path)
221 struct group *grp, grpm;
222 char buf[1024];
224 if (getgrnam_r(v, &grpm, buf, sizeof (buf), &grp) == 0 && grp != NULL)
225 return (grp->gr_gid);
227 if (alldigits(v))
228 return (atoi(v));
230 *ok = B_FALSE;
231 syslog(LOG_ERR, "%s: %s: unknown groupname\n", path, v);
232 return ((gid_t)-1);
235 static priv_set_t *
236 get_privset(const char *s, boolean_t *ok, char *path)
238 priv_set_t *res;
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;
245 return (res);
248 /*ARGSUSED*/
249 static int
250 ggp_callback(const char *prof, kva_t *attr, void *ctxt, void *vres)
252 priv_set_t *res = vres;
253 char *privs;
255 if (attr == NULL)
256 return (0);
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);
268 return (0);
272 * This routine exists on failure and returns NULL if no granted privileges
273 * are set.
275 static priv_set_t *
276 get_granted_privs(uid_t uid)
278 priv_set_t *res;
279 struct passwd *pwd, pwdm;
280 char buf[1024];
282 if (getpwuid_r(uid, &pwdm, buf, sizeof (buf), &pwd) != 0 || pwd == NULL)
283 return (NULL);
285 res = priv_allocset();
286 if (res == NULL)
287 return (NULL);
289 priv_emptyset(res);
291 (void) _enum_profs(pwd->pw_name, ggp_callback, NULL, res);
293 return (res);
296 static void
297 callback_forced_privs(pfexec_arg_t *pap)
299 execattr_t *exec;
300 char *value;
301 priv_set_t *fset;
302 void *res = alloca(setsz);
304 /* Empty set signifies no forced privileges. */
305 priv_emptyset(res);
307 exec = getexecprof("Forced Privilege", KV_COMMAND, pap->pfa_path,
308 GET_ONE);
310 if (exec == NULL && removeisapath(pap->pfa_path)) {
311 exec = getexecprof("Forced Privilege", KV_COMMAND,
312 pap->pfa_path, GET_ONE);
315 if (exec == NULL) {
316 (void) door_return(res, setsz, NULL, 0);
317 return;
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;
327 priv_copyset(fset, res);
328 priv_freeset(fset);
330 free_execattr(exec);
331 (void) door_return(res, setsz, NULL, 0);
334 static void
335 callback_user_privs(pfexec_arg_t *pap)
337 priv_set_t *gset, *wset;
338 uint32_t res;
340 wset = (priv_set_t *)&pap->pfa_buf;
341 gset = get_granted_privs(pap->pfa_uid);
343 res = priv_issubset(wset, gset);
344 priv_freeset(gset);
346 (void) door_return((char *)&res, sizeof (res), NULL, 0);
349 static void
350 callback_pfexec(pfexec_arg_t *pap)
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 = NULL;
358 char *value;
359 priv_set_t *lset, *iset;
360 size_t mysz = repsz - 2 * setsz;
361 char *path = pap->pfa_path;
364 * Initialize the pfexec_reply_t to a sane state.
366 res->pfr_vers = pap->pfa_vers;
367 res->pfr_len = 0;
368 res->pfr_ruid = PFEXEC_NOTSET;
369 res->pfr_euid = PFEXEC_NOTSET;
370 res->pfr_rgid = PFEXEC_NOTSET;
371 res->pfr_egid = PFEXEC_NOTSET;
372 res->pfr_setcred = B_FALSE;
373 res->pfr_scrubenv = B_TRUE;
374 res->pfr_allowed = B_FALSE;
375 res->pfr_ioff = 0;
376 res->pfr_loff = 0;
378 uuid = pap->pfa_uid;
380 if (getpwuid_r(uuid, &pw, buf, sizeof (buf), &pwd) != 0 || pwd == NULL)
381 goto stdexec;
383 exec = getexecuser(pwd->pw_name, KV_COMMAND, path, GET_ONE);
385 if ((exec == NULL || exec->attr == NULL) && removeisapath(path)) {
386 free_execattr(exec);
387 exec = getexecuser(pwd->pw_name, KV_COMMAND, path, GET_ONE);
390 if (exec == NULL) {
391 res->pfr_allowed = B_FALSE;
392 goto ret;
395 if (exec->attr == NULL)
396 goto stdexec;
398 /* Found in execattr, so clearly we can use it */
399 res->pfr_allowed = B_TRUE;
401 uid = euid = (uid_t)-1;
402 gid = egid = (gid_t)-1;
403 lset = iset = NULL;
406 * If there's an error in parsing uid, gid, privs, then return
407 * failure.
409 if ((value = kva_match(exec->attr, EXECATTR_UID_KW)) != NULL)
410 euid = uid = get_uid(value, &res->pfr_allowed, path);
412 if ((value = kva_match(exec->attr, EXECATTR_GID_KW)) != NULL)
413 egid = gid = get_gid(value, &res->pfr_allowed, path);
415 if ((value = kva_match(exec->attr, EXECATTR_EUID_KW)) != NULL)
416 euid = get_uid(value, &res->pfr_allowed, path);
418 if ((value = kva_match(exec->attr, EXECATTR_EGID_KW)) != NULL)
419 egid = get_gid(value, &res->pfr_allowed, path);
421 if ((value = kva_match(exec->attr, EXECATTR_LPRIV_KW)) != NULL)
422 lset = get_privset(value, &res->pfr_allowed, path);
424 if ((value = kva_match(exec->attr, EXECATTR_IPRIV_KW)) != NULL)
425 iset = get_privset(value, &res->pfr_allowed, path);
428 * Remove LD_* variables in the kernel when the runtime linker might
429 * use them later on because the uids are equal.
431 res->pfr_scrubenv = (uid != (uid_t)-1 && euid == uid) ||
432 (gid != (gid_t)-1 && egid == gid) || iset != NULL;
434 res->pfr_euid = euid;
435 res->pfr_ruid = uid;
436 res->pfr_egid = egid;
437 res->pfr_rgid = gid;
439 /* Now add the privilege sets */
440 res->pfr_ioff = res->pfr_loff = 0;
441 if (iset != NULL) {
442 res->pfr_ioff = mysz;
443 priv_copyset(iset, PFEXEC_REPLY_IPRIV(res));
444 mysz += setsz;
445 priv_freeset(iset);
447 if (lset != NULL) {
448 res->pfr_loff = mysz;
449 priv_copyset(lset, PFEXEC_REPLY_LPRIV(res));
450 mysz += setsz;
451 priv_freeset(lset);
454 res->pfr_setcred = uid != (uid_t)-1 || euid != (uid_t)-1 ||
455 egid != (gid_t)-1 || gid != (gid_t)-1 || iset != NULL ||
456 lset != NULL;
458 /* If the real uid changes, we stop running under a profile shell */
459 res->pfr_clearflag = uid != (uid_t)-1 && uid != uuid;
460 free_execattr(exec);
461 ret:
462 (void) door_return((char *)res, mysz, NULL, 0);
463 return;
465 stdexec:
466 free_execattr(exec);
468 res->pfr_scrubenv = B_FALSE;
469 res->pfr_setcred = B_FALSE;
470 res->pfr_allowed = B_TRUE;
472 (void) door_return((char *)res, mysz, NULL, 0);
475 /* ARGSUSED */
476 static void
477 callback(void *cookie, char *argp, size_t asz, door_desc_t *dp, uint_t ndesc)
479 /* LINTED ALIGNMENT */
480 pfexec_arg_t *pap = (pfexec_arg_t *)argp;
482 if (asz < sizeof (pfexec_arg_t) || pap->pfa_vers != PFEXEC_ARG_VERS) {
483 (void) door_return(NULL, 0, NULL, 0);
484 return;
487 switch (pap->pfa_call) {
488 case PFEXEC_EXEC_ATTRS:
489 callback_pfexec(pap);
490 break;
491 case PFEXEC_FORCED_PRIVS:
492 callback_forced_privs(pap);
493 break;
494 case PFEXEC_USER_PRIVS:
495 callback_user_privs(pap);
496 break;
497 default:
498 syslog(LOG_ERR, "Bad Call: %d\n", pap->pfa_call);
499 break;
503 * If the door_return(ptr, size, NULL, 0) fails, make sure we
504 * don't lose server threads.
506 (void) door_return(NULL, 0, NULL, 0);
510 main(void)
512 const priv_impl_info_t *info;
514 (void) signal(SIGINT, unregister_pfexec);
515 (void) signal(SIGQUIT, unregister_pfexec);
516 (void) signal(SIGTERM, unregister_pfexec);
517 (void) signal(SIGHUP, unregister_pfexec);
519 info = getprivimplinfo();
520 if (info == NULL)
521 exit(1);
523 if (fork() > 0)
524 _exit(0);
526 openlog("pfexecd", LOG_PID, LOG_DAEMON);
527 setsz = info->priv_setsize * sizeof (priv_chunk_t);
528 repsz = 2 * setsz + sizeof (pfexec_reply_t);
530 init_isa_regex();
532 doorfd = door_create(callback, NULL, DOOR_REFUSE_DESC);
534 if (doorfd == -1 || register_pfexec(doorfd) != 0) {
535 perror("doorfd");
536 exit(1);
539 /* LINTED CONSTCOND */
540 while (1)
541 (void) sigpause(SIGINT);
543 return (0);