diff options
Diffstat (limited to 'usr/src/cmd/pfexecd/pfexecd.c')
| -rw-r--r-- | usr/src/cmd/pfexecd/pfexecd.c | 525 |
1 files changed, 525 insertions, 0 deletions
diff --git a/usr/src/cmd/pfexecd/pfexecd.c b/usr/src/cmd/pfexecd/pfexecd.c new file mode 100644 index 0000000000..b043c33e9c --- /dev/null +++ b/usr/src/cmd/pfexecd/pfexecd.c @@ -0,0 +1,525 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + * + * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + * + */ + +#define _POSIX_PTHREAD_SEMANTICS 1 + +#include <sys/param.h> +#include <sys/klpd.h> +#include <sys/syscall.h> +#include <sys/systeminfo.h> + +#include <alloca.h> +#include <ctype.h> +#include <deflt.h> +#include <door.h> +#include <errno.h> +#include <grp.h> +#include <priv.h> +#include <pwd.h> +#include <regex.h> +#include <secdb.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include <auth_attr.h> +#include <exec_attr.h> +#include <prof_attr.h> +#include <user_attr.h> + +static int doorfd = -1; + +static size_t repsz, setsz; + +static uid_t get_uid(const char *, boolean_t *, char *); +static gid_t get_gid(const char *, boolean_t *, char *); +static priv_set_t *get_privset(const char *, boolean_t *, char *); +static priv_set_t *get_granted_privs(uid_t); + +/* + * Remove the isaexec path of an executable if we can't find the + * executable at the first attempt. + */ + +static regex_t regc; +static boolean_t cansplice = B_TRUE; + +static void +init_isa_regex(void) +{ + char *isalist; + size_t isalen = 255; /* wild guess */ + size_t len; + long ret; + char *regexpr; + char *p; + + /* + * Extract the isalist(5) for userland from the kernel. + */ + isalist = malloc(isalen); + do { + ret = sysinfo(SI_ISALIST, isalist, isalen); + if (ret == -1l) { + free(isalist); + return; + } + if (ret > isalen) { + isalen = ret; + isalist = realloc(isalist, isalen); + } else + break; + } while (isalist != NULL); + + + if (isalist == NULL) + return; + + /* allocate room for the regex + (/())/[^/]*$ + needed \\. */ +#define LEFT "(/(" +#define RIGHT "))/[^/]*$" + + regexpr = alloca(ret * 2 + sizeof (LEFT RIGHT)); + (void) strcpy(regexpr, LEFT); + len = strlen(regexpr); + + for (p = isalist; *p; p++) { + switch (*p) { + case '+': + case '|': + case '*': + case '[': + case ']': + case '{': + case '}': + case '\\': + regexpr[len++] = '\\'; + default: + regexpr[len++] = *p; + break; + case ' ': + case '\t': + regexpr[len++] = '|'; + break; + } + } + + free(isalist); + regexpr[len] = '\0'; + (void) strcat(regexpr, RIGHT); + + if (regcomp(®c, regexpr, REG_EXTENDED) != 0) + return; + + cansplice = B_TRUE; +} + +#define NMATCH 2 + +static boolean_t +removeisapath(char *path) +{ + regmatch_t match[NMATCH]; + + if (!cansplice || regexec(®c, path, NMATCH, match, 0) != 0) + return (B_FALSE); + + /* + * The first match includes the whole matched expression including the + * end of the string. The second match includes the "/" + "isa" and + * that is the part we need to remove. + */ + + if (match[1].rm_so == -1) + return (B_FALSE); + + /* match[0].rm_eo == strlen(path) */ + (void) memmove(path + match[1].rm_so, path + match[1].rm_eo, + match[0].rm_eo - match[1].rm_eo + 1); + + return (B_TRUE); +} + +static int +register_pfexec(int fd) +{ + int ret = syscall(SYS_privsys, PRIVSYS_PFEXEC_REG, fd); + + return (ret); +} + +/* ARGSUSED */ +static void +unregister_pfexec(int sig) +{ + if (doorfd != -1) + (void) syscall(SYS_privsys, PRIVSYS_PFEXEC_UNREG, doorfd); + _exit(0); +} + +static int +alldigits(const char *s) +{ + int c; + + if (*s == '\0') + return (0); + + while ((c = *s++) != '\0') { + if (!isdigit(c)) { + return (0); + } + } + + return (1); +} + +static uid_t +get_uid(const char *v, boolean_t *ok, char *path) +{ + struct passwd *pwd, pwdm; + char buf[1024]; + + if (getpwnam_r(v, &pwdm, buf, sizeof (buf), &pwd) == 0 && pwd != NULL) + return (pwd->pw_uid); + + if (alldigits(v)) + return (atoi(v)); + + *ok = B_FALSE; + syslog(LOG_ERR, "%s: %s: unknown username\n", path, v); + return ((uid_t)-1); +} + +static uid_t +get_gid(const char *v, boolean_t *ok, char *path) +{ + struct group *grp, grpm; + char buf[1024]; + + if (getgrnam_r(v, &grpm, buf, sizeof (buf), &grp) == 0 && grp != NULL) + return (grp->gr_gid); + + if (alldigits(v)) + return (atoi(v)); + + *ok = B_FALSE; + syslog(LOG_ERR, "%s: %s: unknown groupname\n", path, v); + return ((gid_t)-1); +} + +static priv_set_t * +get_privset(const char *s, boolean_t *ok, char *path) +{ + priv_set_t *res; + + if ((res = priv_str_to_set(s, ",", NULL)) == NULL) { + syslog(LOG_ERR, "%s: %s: bad privilege set\n", path, s); + if (ok != NULL) + *ok = B_FALSE; + } + return (res); +} + +/*ARGSUSED*/ +static int +ggp_callback(const char *prof, kva_t *attr, void *ctxt, void *vres) +{ + priv_set_t *res = vres; + char *privs; + + if (attr == NULL) + return (0); + + /* get privs from this profile */ + privs = kva_match(attr, PROFATTR_PRIVS_KW); + if (privs != NULL) { + priv_set_t *tmp = priv_str_to_set(privs, ",", NULL); + if (tmp != NULL) { + priv_union(tmp, res); + priv_freeset(tmp); + } + } + + return (0); +} + +/* + * This routine exists on failure and returns NULL if no granted privileges + * are set. + */ +static priv_set_t * +get_granted_privs(uid_t uid) +{ + priv_set_t *res; + struct passwd *pwd, pwdm; + char buf[1024]; + + if (getpwuid_r(uid, &pwdm, buf, sizeof (buf), &pwd) != 0 || pwd == NULL) + return (NULL); + + res = priv_allocset(); + if (res == NULL) + return (NULL); + + priv_emptyset(res); + + (void) _enum_profs(pwd->pw_name, ggp_callback, NULL, res); + + return (res); +} + +static void +callback_forced_privs(pfexec_arg_t *pap) +{ + execattr_t *exec; + char *value; + priv_set_t *fset; + void *res = alloca(setsz); + + /* Empty set signifies no forced privileges. */ + priv_emptyset(res); + + exec = getexecprof("Forced Privilege", KV_COMMAND, pap->pfa_path, + GET_ONE); + + if (exec == NULL && removeisapath(pap->pfa_path)) { + exec = getexecprof("Forced Privilege", KV_COMMAND, + pap->pfa_path, GET_ONE); + } + + if (exec == NULL) { + (void) door_return(res, setsz, NULL, 0); + return; + } + + if ((value = kva_match(exec->attr, EXECATTR_IPRIV_KW)) == NULL || + (fset = get_privset(value, NULL, pap->pfa_path)) == NULL) { + free_execattr(exec); + (void) door_return(res, setsz, NULL, 0); + return; + } + + priv_copyset(fset, res); + priv_freeset(fset); + + free_execattr(exec); + (void) door_return(res, setsz, NULL, 0); +} + +static void +callback_user_privs(pfexec_arg_t *pap) +{ + priv_set_t *gset, *wset; + uint32_t res; + + wset = (priv_set_t *)&pap->pfa_buf; + gset = get_granted_privs(pap->pfa_uid); + + res = priv_issubset(wset, gset); + priv_freeset(gset); + + (void) door_return((char *)&res, sizeof (res), NULL, 0); +} + +static void +callback_pfexec(pfexec_arg_t *pap) +{ + pfexec_reply_t *res = alloca(repsz); + uid_t uid, euid, uuid; + gid_t gid, egid; + struct passwd pw, *pwd; + char buf[1024]; + execattr_t *exec; + char *value; + priv_set_t *lset, *iset; + size_t mysz = repsz - 2 * setsz; + char *path = pap->pfa_path; + + uuid = pap->pfa_uid; + + if (getpwuid_r(uuid, &pw, buf, sizeof (buf), &pwd) != 0 || pwd == NULL) + goto stdexec; + + exec = getexecuser(pwd->pw_name, KV_COMMAND, path, GET_ONE); + + if (exec == NULL && removeisapath(path)) + exec = getexecuser(pwd->pw_name, KV_COMMAND, path, GET_ONE); + + if (exec == NULL) { + res->pfr_allowed = B_FALSE; + goto ret; + } + + if (exec->attr == NULL) + goto stdexec; + + /* Found in execattr, so clearly we can use it */ + res->pfr_allowed = B_TRUE; + + uid = euid = (uid_t)-1; + gid = egid = (gid_t)-1; + lset = iset = NULL; + + /* + * If there's an error in parsing uid, gid, privs, then return + * failure. + */ + if ((value = kva_match(exec->attr, EXECATTR_UID_KW)) != NULL) + euid = uid = get_uid(value, &res->pfr_allowed, path); + + if ((value = kva_match(exec->attr, EXECATTR_GID_KW)) != NULL) + egid = gid = get_gid(value, &res->pfr_allowed, path); + + if ((value = kva_match(exec->attr, EXECATTR_EUID_KW)) != NULL) + euid = get_uid(value, &res->pfr_allowed, path); + + if ((value = kva_match(exec->attr, EXECATTR_EGID_KW)) != NULL) + egid = get_gid(value, &res->pfr_allowed, path); + + if ((value = kva_match(exec->attr, EXECATTR_LPRIV_KW)) != NULL) + lset = get_privset(value, &res->pfr_allowed, path); + + if ((value = kva_match(exec->attr, EXECATTR_IPRIV_KW)) != NULL) + iset = get_privset(value, &res->pfr_allowed, path); + + /* + * Remove LD_* variables in the kernel when the runtime linker might + * use them later on because the uids are equal. + */ + res->pfr_scrubenv = (uid != (uid_t)-1 && euid == uid) || + (gid != (gid_t)-1 && egid == gid) || iset != NULL; + + res->pfr_euid = euid; + res->pfr_ruid = uid; + res->pfr_egid = egid; + res->pfr_rgid = gid; + + /* Now add the privilege sets */ + res->pfr_ioff = res->pfr_loff = 0; + if (iset != NULL) { + res->pfr_ioff = mysz; + priv_copyset(iset, PFEXEC_REPLY_IPRIV(res)); + mysz += setsz; + priv_freeset(iset); + } + if (lset != NULL) { + res->pfr_loff = mysz; + priv_copyset(lset, PFEXEC_REPLY_LPRIV(res)); + mysz += setsz; + priv_freeset(lset); + } + + res->pfr_setcred = uid != (uid_t)-1 || euid != (uid_t)-1 || + egid != (gid_t)-1 || gid != (gid_t)-1 || iset != NULL || + lset != NULL; + + /* If the real uid changes, we stop running under a profile shell */ + res->pfr_clearflag = uid != (uid_t)-1 && uid != uuid; + free_execattr(exec); +ret: + (void) door_return((char *)res, mysz, NULL, 0); + return; + +stdexec: + res->pfr_scrubenv = B_FALSE; + res->pfr_setcred = B_FALSE; + res->pfr_allowed = B_TRUE; + + (void) door_return((char *)res, mysz, NULL, 0); +} + +/* ARGSUSED */ +static void +callback(void *cookie, char *argp, size_t asz, door_desc_t *dp, uint_t ndesc) +{ + /* LINTED ALIGNMENT */ + pfexec_arg_t *pap = (pfexec_arg_t *)argp; + + if (asz < sizeof (pfexec_arg_t) || pap->pfa_vers != PFEXEC_ARG_VERS) { + (void) door_return(NULL, 0, NULL, 0); + return; + } + + switch (pap->pfa_call) { + case PFEXEC_EXEC_ATTRS: + callback_pfexec(pap); + break; + case PFEXEC_FORCED_PRIVS: + callback_forced_privs(pap); + break; + case PFEXEC_USER_PRIVS: + callback_user_privs(pap); + break; + default: + syslog(LOG_ERR, "Bad Call: %d\n", pap->pfa_call); + break; + } + + /* + * If the door_return(ptr, size, NULL, 0) fails, make sure we + * don't lose server threads. + */ + (void) door_return(NULL, 0, NULL, 0); +} + +int +main(void) +{ + const priv_impl_info_t *info; + + (void) signal(SIGINT, unregister_pfexec); + (void) signal(SIGQUIT, unregister_pfexec); + (void) signal(SIGTERM, unregister_pfexec); + (void) signal(SIGHUP, unregister_pfexec); + + info = getprivimplinfo(); + if (info == NULL) + exit(1); + + if (fork() > 0) + _exit(0); + + openlog("pfexecd", LOG_PID, LOG_DAEMON); + setsz = info->priv_setsize * sizeof (priv_chunk_t); + repsz = 2 * setsz + sizeof (pfexec_reply_t); + + init_isa_regex(); + + doorfd = door_create(callback, NULL, DOOR_REFUSE_DESC); + + if (doorfd == -1 || register_pfexec(doorfd) != 0) { + perror("doorfd"); + exit(1); + } + + /* LINTED CONSTCOND */ + while (1) + (void) sigpause(SIGINT); + + return (0); +} |
