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(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(®c, regexpr, REG_EXTENDED) != 0)
136 return;
137
138 cansplice = B_TRUE;
139 }
140
141 #define NMATCH 2
142
143 static boolean_t
removeisapath(char * path)144 removeisapath(char *path)
145 {
146 regmatch_t match[NMATCH];
147
148 if (!cansplice || regexec(®c, 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
register_pfexec(int fd)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
unregister_pfexec(int sig)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
alldigits(const char * s)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
get_uid(const char * v,boolean_t * ok,char * path)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
get_gid(const char * v,boolean_t * ok,char * path)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 *
get_privset(const char * s,boolean_t * ok,char * path)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
ggp_callback(const char * prof,kva_t * attr,void * ctxt,void * vres)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 *
get_granted_privs(uid_t uid)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
callback_forced_privs(pfexec_arg_t * pap)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
callback_user_privs(pfexec_arg_t * pap)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
callback_pfexec(pfexec_arg_t * pap)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 = NULL;
358 char *value;
359 priv_set_t *lset, *iset;
360 size_t mysz = repsz - 2 * setsz;
361 char *path = pap->pfa_path;
362
363 /*
364 * Initialize the pfexec_reply_t to a sane state.
365 */
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;
377
378 uuid = pap->pfa_uid;
379
380 if (getpwuid_r(uuid, &pw, buf, sizeof (buf), &pwd) != 0 || pwd == NULL)
381 goto stdexec;
382
383 exec = getexecuser(pwd->pw_name, KV_COMMAND, path, GET_ONE);
384
385 if ((exec == NULL || exec->attr == NULL) && removeisapath(path)) {
386 free_execattr(exec);
387 exec = getexecuser(pwd->pw_name, KV_COMMAND, path, GET_ONE);
388 }
389
390 if (exec == NULL) {
391 res->pfr_allowed = B_FALSE;
392 goto ret;
393 }
394
395 if (exec->attr == NULL)
396 goto stdexec;
397
398 /* Found in execattr, so clearly we can use it */
399 res->pfr_allowed = B_TRUE;
400
401 uid = euid = (uid_t)-1;
402 gid = egid = (gid_t)-1;
403 lset = iset = NULL;
404
405 /*
406 * If there's an error in parsing uid, gid, privs, then return
407 * failure.
408 */
409 if ((value = kva_match(exec->attr, EXECATTR_UID_KW)) != NULL)
410 euid = uid = get_uid(value, &res->pfr_allowed, path);
411
412 if ((value = kva_match(exec->attr, EXECATTR_GID_KW)) != NULL)
413 egid = gid = get_gid(value, &res->pfr_allowed, path);
414
415 if ((value = kva_match(exec->attr, EXECATTR_EUID_KW)) != NULL)
416 euid = get_uid(value, &res->pfr_allowed, path);
417
418 if ((value = kva_match(exec->attr, EXECATTR_EGID_KW)) != NULL)
419 egid = get_gid(value, &res->pfr_allowed, path);
420
421 if ((value = kva_match(exec->attr, EXECATTR_LPRIV_KW)) != NULL)
422 lset = get_privset(value, &res->pfr_allowed, path);
423
424 if ((value = kva_match(exec->attr, EXECATTR_IPRIV_KW)) != NULL)
425 iset = get_privset(value, &res->pfr_allowed, path);
426
427 /*
428 * Remove LD_* variables in the kernel when the runtime linker might
429 * use them later on because the uids are equal.
430 */
431 res->pfr_scrubenv = (uid != (uid_t)-1 && euid == uid) ||
432 (gid != (gid_t)-1 && egid == gid) || iset != NULL;
433
434 res->pfr_euid = euid;
435 res->pfr_ruid = uid;
436 res->pfr_egid = egid;
437 res->pfr_rgid = gid;
438
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);
446 }
447 if (lset != NULL) {
448 res->pfr_loff = mysz;
449 priv_copyset(lset, PFEXEC_REPLY_LPRIV(res));
450 mysz += setsz;
451 priv_freeset(lset);
452 }
453
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;
457
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;
464
465 stdexec:
466 free_execattr(exec);
467
468 res->pfr_scrubenv = B_FALSE;
469 res->pfr_setcred = B_FALSE;
470 res->pfr_allowed = B_TRUE;
471
472 (void) door_return((char *)res, mysz, NULL, 0);
473 }
474
475 /* ARGSUSED */
476 static void
callback(void * cookie,char * argp,size_t asz,door_desc_t * dp,uint_t ndesc)477 callback(void *cookie, char *argp, size_t asz, door_desc_t *dp, uint_t ndesc)
478 {
479 /* LINTED ALIGNMENT */
480 pfexec_arg_t *pap = (pfexec_arg_t *)argp;
481
482 if (asz < sizeof (pfexec_arg_t) || pap->pfa_vers != PFEXEC_ARG_VERS) {
483 (void) door_return(NULL, 0, NULL, 0);
484 return;
485 }
486
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;
500 }
501
502 /*
503 * If the door_return(ptr, size, NULL, 0) fails, make sure we
504 * don't lose server threads.
505 */
506 (void) door_return(NULL, 0, NULL, 0);
507 }
508
509 int
main(void)510 main(void)
511 {
512 const priv_impl_info_t *info;
513
514 (void) signal(SIGINT, unregister_pfexec);
515 (void) signal(SIGQUIT, unregister_pfexec);
516 (void) signal(SIGTERM, unregister_pfexec);
517 (void) signal(SIGHUP, unregister_pfexec);
518
519 info = getprivimplinfo();
520 if (info == NULL)
521 exit(1);
522
523 if (fork() > 0)
524 _exit(0);
525
526 openlog("pfexecd", LOG_PID, LOG_DAEMON);
527 setsz = info->priv_setsize * sizeof (priv_chunk_t);
528 repsz = 2 * setsz + sizeof (pfexec_reply_t);
529
530 init_isa_regex();
531
532 doorfd = door_create(callback, NULL, DOOR_REFUSE_DESC);
533
534 if (doorfd == -1 || register_pfexec(doorfd) != 0) {
535 perror("doorfd");
536 exit(1);
537 }
538
539 /* LINTED CONSTCOND */
540 while (1)
541 (void) sigpause(SIGINT);
542
543 return (0);
544 }
545