summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cifs.upcall.8.in9
-rw-r--r--cifs.upcall.c150
2 files changed, 152 insertions, 7 deletions
diff --git a/cifs.upcall.8.in b/cifs.upcall.8.in
index 50f79d1..e1f3956 100644
--- a/cifs.upcall.8.in
+++ b/cifs.upcall.8.in
@@ -38,6 +38,15 @@ for a particular key type\&. While it can be run directly from the command\-line
This option is deprecated and is currently ignored\&.
.RE
.PP
+\-\-no-env-probe|\-E
+.RS 4
+Normally, cifs.upcall will probe the environment variable space of the process
+that initiated the upcall in order to fetch the value of $KRB5CCNAME. This can
+assist the program with finding credential caches in non-default locations. If
+this option is set, then the program won't do this and will rely on finding
+credcaches in the default locations specified in krb5.conf.
+.RE
+.PP
\--krb5conf=/path/to/krb5.conf|-k /path/to/krb5.conf
.RS 4
This option allows administrators to set an alternate location for the
diff --git a/cifs.upcall.c b/cifs.upcall.c
index 0bd3dcb..6d9c427 100644
--- a/cifs.upcall.c
+++ b/cifs.upcall.c
@@ -40,6 +40,7 @@
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
+#include <fcntl.h>
#include <unistd.h>
#include <keyutils.h>
#include <time.h>
@@ -213,11 +214,125 @@ err_cache:
return credtime;
}
+#define ENV_PATH_FMT "/proc/%d/environ"
+#define ENV_PATH_MAXLEN (6 + 10 + 8 + 1)
+
+#define ENV_NAME "KRB5CCNAME"
+#define ENV_PREFIX "KRB5CCNAME="
+#define ENV_PREFIX_LEN 11
+
+#define ENV_BUF_START (4096)
+#define ENV_BUF_MAX (131072)
+
+/**
+ * get_cachename_from_process_env - scrape value of $KRB5CCNAME out of the
+ * initiating process' environment.
+ * @pid: initiating pid value from the upcall string
+ *
+ * Open the /proc/<pid>/environ file for the given pid, and scrape it for
+ * KRB5CCNAME entries.
+ *
+ * We start with a page-size buffer, and then progressively double it until
+ * we can slurp in the whole thing.
+ *
+ * Note that this is not entirely reliable. If the process is sitting in a
+ * container or something, then this is almost certainly not going to point
+ * where you expect.
+ *
+ * Probably it just won't work, but could a user use this to trick cifs.upcall
+ * into reading a file outside the container, by setting KRB5CCNAME in a
+ * crafty way?
+ */
+static char *
+get_cachename_from_process_env(pid_t pid)
+{
+ int fd, ret;
+ ssize_t buflen;
+ ssize_t bufsize = ENV_BUF_START;
+ char pathname[ENV_PATH_MAXLEN];
+ char *cachename = NULL;
+ char *buf = NULL, *pos;
+
+ if (!pid) {
+ syslog(LOG_DEBUG, "%s: pid == 0\n", __func__);
+ return NULL;
+ }
+
+ pathname[ENV_PATH_MAXLEN - 1] = '\0';
+ ret = snprintf(pathname, ENV_PATH_MAXLEN, ENV_PATH_FMT, pid);
+ if (ret >= ENV_PATH_MAXLEN) {
+ syslog(LOG_DEBUG, "%s: unterminated path!\n", __func__);
+ return NULL;
+ }
+
+ syslog(LOG_DEBUG, "%s: pathname=%s\n", __func__, pathname);
+ fd = open(pathname, O_RDONLY);
+ if (fd < 0) {
+ syslog(LOG_DEBUG, "%s: open failed: %d\n", __func__, errno);
+ return NULL;
+ }
+retry:
+ if (bufsize > ENV_BUF_MAX) {
+ syslog(LOG_DEBUG, "%s: buffer too big: %zd\n",
+ __func__, bufsize);
+ goto out_close;
+ }
+
+ buf = malloc(bufsize);
+ if (!buf) {
+ syslog(LOG_DEBUG, "%s: malloc failure\n", __func__);
+ goto out_close;
+ }
+
+ buflen = read(fd, buf, bufsize);
+ if (buflen < 0) {
+ syslog(LOG_DEBUG, "%s: read failed: %d\n", __func__, errno);
+ goto out_close;
+ }
+
+ if (buflen >= bufsize) {
+ /* We read to the end of the buffer. Double and try again */
+ syslog(LOG_DEBUG, "%s: read to end of buffer (%zu bytes)\n",
+ __func__, bufsize);
+ free(buf);
+ bufsize *= 2;
+ if (lseek(fd, 0, SEEK_SET) < 0)
+ goto out_close;
+ goto retry;
+ }
+
+ pos = buf;
+ while (buflen > 0) {
+ size_t len = strnlen(pos, buflen);
+
+ if (len > ENV_PREFIX_LEN &&
+ !memcmp(pos, ENV_PREFIX, ENV_PREFIX_LEN)) {
+ cachename = strndup(pos + ENV_PREFIX_LEN,
+ len - ENV_PREFIX_LEN);
+ syslog(LOG_DEBUG, "%s: cachename = %s\n",
+ __func__, cachename);
+ break;
+ }
+ buflen -= (len + 1);
+ pos += (len + 1);
+ }
+out_close:
+ free(buf);
+ close(fd);
+ return cachename;
+}
+
static krb5_ccache
-get_default_cc(void)
+get_existing_cc(const char *env_cachename)
{
krb5_error_code ret;
krb5_ccache cc;
+ char *cachename;
+
+ if (env_cachename) {
+ if (setenv(ENV_NAME, env_cachename, 1))
+ syslog(LOG_DEBUG, "%s: failed to setenv %d\n", __func__, errno);
+ }
ret = krb5_cc_default(context, &cc);
if (ret) {
@@ -225,6 +340,14 @@ get_default_cc(void)
return NULL;
}
+ ret = krb5_cc_get_full_name(context, cc, &cachename);
+ if (ret) {
+ syslog(LOG_DEBUG, "%s: krb5_cc_get_full_name failed: %d\n", __func__, ret);
+ } else {
+ syslog(LOG_DEBUG, "%s: default ccache is %s\n", __func__, cachename);
+ krb5_free_string(context, cachename);
+ }
+
if (!get_tgt_time(cc)) {
krb5_cc_close(context, cc);
cc = NULL;
@@ -232,7 +355,6 @@ get_default_cc(void)
return cc;
}
-
static krb5_ccache
init_cc_from_keytab(const char *keytab_name, const char *user)
{
@@ -723,10 +845,11 @@ lowercase_string(char *c)
static void usage(void)
{
- fprintf(stderr, "Usage: %s [ -K /path/to/keytab] [-k /path/to/krb5.conf] [-t] [-v] [-l] key_serial\n", prog);
+ fprintf(stderr, "Usage: %s [ -K /path/to/keytab] [-k /path/to/krb5.conf] [-E] [-t] [-v] [-l] key_serial\n", prog);
}
static const struct option long_options[] = {
+ {"no-env-probe", 0, NULL, 'E'},
{"krb5conf", 1, NULL, 'k'},
{"legacy-uid", 0, NULL, 'l'},
{"trust-dns", 0, NULL, 't'},
@@ -745,13 +868,14 @@ int main(const int argc, char *const argv[])
unsigned int have;
long rc = 1;
int c;
- bool try_dns = false, legacy_uid = false;
+ bool try_dns = false, legacy_uid = false , env_probe = true;
char *buf;
char hostbuf[NI_MAXHOST], *host;
struct decoded_args arg;
const char *oid;
uid_t uid;
char *keytab_name = NULL;
+ char *env_cachename = NULL;
krb5_ccache ccache = NULL;
struct passwd *pw;
@@ -760,11 +884,15 @@ int main(const int argc, char *const argv[])
openlog(prog, 0, LOG_DAEMON);
- while ((c = getopt_long(argc, argv, "ck:K:ltv", long_options, NULL)) != -1) {
+ while ((c = getopt_long(argc, argv, "cEk:K:ltv", long_options, NULL)) != -1) {
switch (c) {
case 'c':
/* legacy option -- skip it */
break;
+ case 'E':
+ /* skip probing initiating process env */
+ env_probe = false;
+ break;
case 't':
try_dns = true;
break;
@@ -790,7 +918,7 @@ int main(const int argc, char *const argv[])
}
}
- if (trim_capabilities(false))
+ if (trim_capabilities(env_probe))
goto out;
/* is there a key? */
@@ -890,6 +1018,13 @@ int main(const int argc, char *const argv[])
goto out;
}
+ /*
+ * Must do this before setuid, as we need elevated capabilities to
+ * look at the environ file.
+ */
+ env_cachename =
+ get_cachename_from_process_env(env_probe ? arg.pid : 0);
+
rc = setuid(uid);
if (rc == -1) {
syslog(LOG_ERR, "setuid: %s", strerror(errno));
@@ -908,7 +1043,7 @@ int main(const int argc, char *const argv[])
goto out;
}
- ccache = get_default_cc();
+ ccache = get_existing_cc(env_cachename);
/* Couldn't find credcache? Try to use keytab */
if (ccache == NULL && arg.username != NULL)
ccache = init_cc_from_keytab(keytab_name, arg.username);
@@ -1061,6 +1196,7 @@ out:
SAFE_FREE(arg.ip);
SAFE_FREE(arg.username);
SAFE_FREE(keydata);
+ SAFE_FREE(env_cachename);
syslog(LOG_DEBUG, "Exit status %ld", rc);
return rc;
}