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
init_isa_regex(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(®c, regexpr, REG_EXTENDED) != 0)
137 return;
138
139 cansplice = B_TRUE;
140 }
141
142 #define NMATCH 2
143
144 static boolean_t
removeisapath(char * path)145 removeisapath(char *path)
146 {
147 regmatch_t match[NMATCH];
148
149 if (!cansplice || regexec(®c, 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
register_pfexec(int fd)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
unregister_pfexec(int sig)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
alldigits(const char * s)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
get_uid(const char * v,boolean_t * ok,char * path)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
get_gid(const char * v,boolean_t * ok,char * path)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 *
get_privset(const char * s,boolean_t * ok,char * path)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
ggp_callback(const char * prof,kva_t * attr,void * ctxt,void * vres)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 *
get_granted_privs(uid_t uid)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
callback_forced_privs(pfexec_arg_t * pap)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
callback_user_privs(pfexec_arg_t * pap)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
callback_pfexec(pfexec_arg_t * pap)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
callback(void * cookie,char * argp,size_t asz,door_desc_t * dp,uint_t ndesc)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
main(void)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