From patchwork Wed Feb 15 16:13:23 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jeff Layton X-Patchwork-Id: 9574361 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id BF21E60586 for ; Wed, 15 Feb 2017 16:13:47 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id AD0C82846B for ; Wed, 15 Feb 2017 16:13:47 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id A21CE2808F; Wed, 15 Feb 2017 16:13:47 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.8 required=2.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_HI,T_DKIM_INVALID autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 881B5284E8 for ; Wed, 15 Feb 2017 16:13:46 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751383AbdBOQNq (ORCPT ); Wed, 15 Feb 2017 11:13:46 -0500 Received: from hr2.samba.org ([144.76.82.148]:33903 "EHLO hr2.samba.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1750905AbdBOQNp (ORCPT ); Wed, 15 Feb 2017 11:13:45 -0500 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=samba.org; s=42627210; h=Message-Id:Date:Cc:To:From; bh=sTnXrvVhD8/+PxtRMxhS3KIrBs9IraHNNJZVNiIUlF8=; b=ItenPtxwqZmhsJOWpN7y9Wa0NH bCKkTOjQP8MUkcv6uGgjRlMo38cjQLRIkM6/r9EV7BDFl2UhBIxjdiSs3SioJJ7xvDyz05MnyP+0l mxkYW2JSWAylPjTiPPNu/wV6RLApPlVLvNsLEb7kyBWdyt/YcKNVk+HT5vWfauOl7DNg=; Received: from [127.0.0.2] (localhost [127.0.0.1]) by hr2.samba.org with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim) id 1ce2DE-0006dx-Im; Wed, 15 Feb 2017 16:13:48 +0000 From: Jeff Layton To: linux-cifs@vger.kernel.org, samba-technical@lists.samba.org Cc: cwseys@physics.wisc.edu, simo@redhat.com, samba@lists.samba.org Subject: [cifs-utils PATCHv3 3/3] cifs.upcall: allow scraping of KRB5CCNAME out of initiating task's /proc//environ file Date: Wed, 15 Feb 2017 11:13:23 -0500 Message-Id: <20170215161325.16914-4-jlayton@samba.org> X-Mailer: git-send-email 2.9.3 In-Reply-To: <20170215161325.16914-1-jlayton@samba.org> References: <20170215161325.16914-1-jlayton@samba.org> Sender: linux-cifs-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-cifs@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Chad reported that he was seeing a regression in cifs-utils-6.6. Prior to that, cifs.upcall was able to find credcaches in non-default FILE: locations, but with the rework of that code, that ability was lost. Unfortunately, the krb5 library design doesn't really take into account the fact that we might need to find a credcache in a process that isn't descended from the session. When the kernel does an upcall, it passes several bits of info about the task that initiated the upcall. One of those things is the PID (the tgid, in particular). We can use that info to reach into the /proc//environ file for the process, and grab whatever value of $KRB5CCNAME is there. Then, after switching credentials, set $KRB5CCNAME in the environment to the same value before opening the credcache, to hint to the krb5 libs where they ought to look. This new behavior is on by default, but can be disabled by having request-key pass a '-E' flag to cifs.upcall. Reported-by: Chad William Seys Signed-off-by: Jeff Layton --- cifs.upcall.8.in | 9 ++++ cifs.upcall.c | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 151 insertions(+), 6 deletions(-) diff --git a/cifs.upcall.8.in b/cifs.upcall.8.in index 50f79d18331d..e1f3956e176a 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 2b535a133a30..3a556e10b593 100644 --- a/cifs.upcall.c +++ b/cifs.upcall.c @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -156,11 +157,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//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) { @@ -168,6 +283,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; @@ -175,7 +298,6 @@ get_default_cc(void) return cc; } - static krb5_ccache init_cc_from_keytab(const char *keytab_name, const char *user) { @@ -666,10 +788,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'}, @@ -688,13 +811,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; @@ -703,11 +827,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; @@ -830,6 +958,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)); @@ -844,7 +979,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); @@ -997,6 +1132,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; }