summaryrefslogtreecommitdiff
path: root/usr/src/cmd/pfexecd/pfexecd.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/cmd/pfexecd/pfexecd.c')
-rw-r--r--usr/src/cmd/pfexecd/pfexecd.c525
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(&regc, regexpr, REG_EXTENDED) != 0)
+ return;
+
+ cansplice = B_TRUE;
+}
+
+#define NMATCH 2
+
+static boolean_t
+removeisapath(char *path)
+{
+ regmatch_t match[NMATCH];
+
+ if (!cansplice || regexec(&regc, 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);
+}