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