Whamcloud - gitweb
LU-17173 gss: user keys go to user keyring 71/52771/14
authorSebastien Buisson <sbuisson@ddn.com>
Fri, 20 Oct 2023 08:27:14 +0000 (10:27 +0200)
committerOleg Drokin <green@whamcloud.com>
Sun, 4 Feb 2024 08:27:45 +0000 (08:27 +0000)
Keys for root, that are used for Lustre internal processing, are
stored in the session keyring. That way they can be found by all
Lustre processes in userspace and in the kernel.
For end user keys, it is better to store them in the user keyring.
This simplifies key management, makes them shared accross all user
sessions, and avoids unfortunate key leak if lfs flushctx is not
called at user logout.

Test-Parameters: kerberos=true testlist=sanity-krb5
Signed-off-by: Sebastien Buisson <sbuisson@ddn.com>
Change-Id: Ibb3d326e89dcacc89e77eca76cdb773861d3a8a7
Reviewed-on: https://review.whamcloud.com/c/fs/lustre-release/+/52771
Tested-by: jenkins <devops@whamcloud.com>
Tested-by: Maloo <maloo@whamcloud.com>
Reviewed-by: Aurelien Degremont <adegremont@nvidia.com>
Reviewed-by: Andreas Dilger <adilger@whamcloud.com>
Reviewed-by: Oleg Drokin <green@whamcloud.com>
libcfs/autoconf/lustre-libcfs.m4
lustre/ptlrpc/gss/gss_keyring.c
lustre/ptlrpc/sec_lproc.c
lustre/tests/sanity-krb5.sh
lustre/utils/gss/lgss_keyring.c
lustre/utils/gss/lgss_krb5_utils.c
lustre/utils/gss/lgss_utils.c
lustre/utils/gss/lgss_utils.h

index 85b4f66..ae3765b 100644 (file)
@@ -2006,6 +2006,27 @@ AC_DEFUN([LIBCFS_KERNEL_SETSOCKOPT], [
 ]) # LIBCFS_KERNEL_SETSOCKOPT
 
 #
+# LIBCFS_USER_UID_KEYRING
+#
+# kernel 5.2 commit 0f44e4d976f9 removed uid_keyring
+# from the user_struct struct
+#
+AC_DEFUN([LIBCFS_SRC_USER_UID_KEYRING], [
+       LB2_LINUX_TEST_SRC([user_uid_keyring_exists], [
+               #include <linux/sched/user.h>
+       ],[
+               ((struct user_struct *)0)->uid_keyring = NULL;
+       ],[-Werror])
+])
+AC_DEFUN([LIBCFS_USER_UID_KEYRING], [
+       AC_MSG_CHECKING([if uid_keyring exists])
+       LB2_LINUX_TEST_RESULT([user_uid_keyring_exists], [
+               AC_DEFINE(HAVE_USER_UID_KEYRING, 1,
+                       [uid_keyring exists])
+       ])
+]) # LIBCFS_USER_UID_KEYRING
+
+#
 # LIBCFS_KEY_NEED_UNLINK
 #
 # kernel 5.8 commit 8c0637e950d68933a67f7438f779d79b049b5e5c
@@ -2469,6 +2490,7 @@ AC_DEFUN([LIBCFS_PROG_LINUX_SRC], [
        LIBCFS_SRC_GET_REQUEST_KEY_AUTH
        # 5.2
        LIBCFS_SRC_KOBJ_TYPE_DEFAULT_GROUPS
+       LIBCFS_SRC_USER_UID_KEYRING
        # 5.3
        LIBCFS_SRC_LOOKUP_USER_KEY
        LIBCFS_SRC_CACHE_DETAIL_WRITERS
@@ -2619,6 +2641,7 @@ AC_DEFUN([LIBCFS_PROG_LINUX_RESULTS], [
        LIBCFS_GET_REQUEST_KEY_AUTH
        # 5.2
        LIBCFS_KOBJ_TYPE_DEFAULT_GROUPS
+       LIBCFS_USER_UID_KEYRING
        # 5.3
        LIBCFS_LOOKUP_USER_KEY
        LIBCFS_CACHE_DETAIL_WRITERS
index 041b06f..3530607 100644 (file)
@@ -367,17 +367,18 @@ static void unbind_key_ctx(struct key *key, struct ptlrpc_cli_ctx *ctx)
        LASSERT(key_get_payload(key, 0) == ctx);
        LASSERT(test_bit(PTLRPC_CTX_CACHED_BIT, &ctx->cc_flags) == 0);
 
-        /* must revoke the key, or others may treat it as newly created */
-        key_revoke_locked(key);
+       /* must revoke the key, or others may treat it as newly created */
+       key_revoke_locked(key);
+       request_key_unlink(key);
 
        key_set_payload(key, 0, NULL);
-        ctx2gctx_keyring(ctx)->gck_key = NULL;
+       ctx2gctx_keyring(ctx)->gck_key = NULL;
 
-        /* once ctx get split from key, the timer is meaningless */
-        ctx_clear_timer_kr(ctx);
+       /* once ctx get split from key, the timer is meaningless */
+       ctx_clear_timer_kr(ctx);
 
-        ctx_put_kr(ctx, 1);
-        key_put(key);
+       ctx_put_kr(ctx, 1);
+       key_put(key);
 }
 
 /*
@@ -397,7 +398,6 @@ static void unbind_ctx_kr(struct ptlrpc_cli_ctx *ctx)
                unbind_key_ctx(key, ctx);
                up_write(&key->sem);
                key_put(key);
-               request_key_unlink(key);
        }
 }
 
@@ -684,14 +684,45 @@ static inline struct key *get_user_keyring(const struct cred *cred)
 /*
  * unlink request key from it's ring, which is linked during request_key().
  * sadly, we have to 'guess' which keyring it's linked to.
- *
- * FIXME this code is fragile, it depends on how request_key() is implemented.
  */
 static void request_key_unlink(struct key *key)
 {
-       const struct cred *cred = current_cred();
+       struct cred *cred = (struct cred *)current_cred(), *new_cred = NULL;
+#ifdef HAVE_USER_UID_KEYRING
+       struct key *root_uid_keyring = NULL;
+#endif
+       const struct cred *old_cred = NULL;
+       kuid_t uid = current_uid();
        struct key *ring = NULL;
+       int res;
+
+       /* unlink key with user's creds if it's a user key */
+       if (!uid_eq(key->uid, current_uid())) {
+               new_cred = prepare_creds();
+               if (new_cred == NULL)
+                       goto search;
+
+               new_cred->uid = key->uid;
+               new_cred->user->uid = key->uid;
+#ifdef HAVE_USER_UID_KEYRING
+               root_uid_keyring = current_cred()->user->uid_keyring;
+               new_cred->user->uid_keyring = NULL;
+#endif
+               old_cred = override_creds(new_cred);
+               cred = new_cred;
+       }
 
+       /* User keys are linked to the user keyring. So get it now. */
+       if (from_kuid(&init_user_ns, key->uid)) {
+               /* Getting a key(ring) normally increases its refcount by 1.
+                * But if we overrode creds above, calling get_user_keyring()
+                * will add one more ref, because of the user switch.
+                */
+               ring = get_user_keyring(cred);
+               goto do_unlink;
+       }
+
+search:
        switch (cred->jit_keyring) {
        case KEY_REQKEY_DEFL_DEFAULT:
        case KEY_REQKEY_DEFL_REQUESTOR_KEYRING:
@@ -738,17 +769,40 @@ static void request_key_unlink(struct key *key)
                LBUG();
        }
 
+do_unlink:
        if (ring) {
-               int res = key_unlink(ring, key);
+               res = key_unlink(ring, key);
                CDEBUG(D_SEC,
                       "Unlink key %08x (%p) from keyring %08x: %d\n",
                       key->serial, key, ring->serial, res);
+               /* matches key_get()/get_user_keyring() above */
                key_put(ring);
+               /* If this is a user key, it added an extra ref on the user
+                * keyring at link/instantiate stage. This ref needs to be
+                * removed now that the key has been unlinked.
+                */
+               if (from_kuid(&init_user_ns, key->uid))
+                       key_put(ring);
        } else {
                CDEBUG(D_SEC,
                       "Missing keyring, key %08x (%p) could not be unlinked, ignored\n",
                       key->serial, key);
        }
+
+       if (old_cred) {
+               revert_creds(old_cred);
+               put_cred(new_cred);
+               current_cred()->user->uid = uid;
+#ifdef HAVE_USER_UID_KEYRING
+               /* We are switching creds back, so need to drop ref on keyring
+                * for kernel implementation based on user keyring pinned from
+                * the user_struct struct.
+                */
+               key_put(ring);
+               if (root_uid_keyring)
+                       current_cred()->user->uid_keyring = root_uid_keyring;
+#endif
+       }
 }
 
 static
@@ -912,6 +966,18 @@ struct ptlrpc_cli_ctx * gss_sec_lookup_ctx_kr(struct ptlrpc_sec *sec,
        }
        CDEBUG(D_SEC, "obtained key %08x for %s\n", key->serial, desc);
 
+       /* We want user keys to be linked to the user keyring (see call to
+        * keyctl_instantiate() from prepare_and_instantiate() in userspace).
+        * But internally request_key() tends to also link the key to the
+        * session keyring. So do our best to avoid that by trying to unlink
+        * the key from the session keyring right now. It will spare us pain
+        * when we need to remove the key later on.
+        */
+       if (!is_root && current_cred()->session_keyring) {
+               key_get(current_cred()->session_keyring);
+               (void)key_unlink(current_cred()->session_keyring, key);
+               key_put(current_cred()->session_keyring);
+       }
        /* once payload.data was pointed to a ctx, it never changes until
         * we de-associate them; but parallel request_key() may return
         * a key with payload.data == NULL at the same time. so we still
@@ -1017,9 +1083,6 @@ void flush_user_ctx_cache_kr(struct ptlrpc_sec *sec,
                key_revoke_locked(key);
 
                up_write(&key->sem);
-
-               request_key_unlink(key);
-
                key_put(key);
        }
 }
@@ -1373,7 +1436,9 @@ int gss_kt_instantiate(struct key *key, struct key_preparsed_payload *prep)
 int gss_kt_instantiate(struct key *key, const void *data, size_t datalen)
 {
 #endif
-       int rc;
+       struct key *keyring;
+       int uid, rc;
+
        ENTRY;
 
        CDEBUG(D_SEC, "instantiating key %08x (%p)\n", key->serial, key);
@@ -1397,23 +1462,29 @@ int gss_kt_instantiate(struct key *key, const void *data, size_t datalen)
         *
         * the session keyring is created upon upcall, and don't change all
         * the way until upcall finished, so rcu lock is not needed here.
+        *
+        * But for end users, link to the user keyring. This simplifies key
+        * management, makes them shared accross all user sessions, and avoids
+        * unfortunate key leak if lfs flushctx is not called at user logout.
         */
-       LASSERT(current_cred()->session_keyring);
+       uid = from_kuid(&init_user_ns, current_uid());
+       if (uid == 0)
+               keyring = current_cred()->session_keyring;
+       else
+               keyring = get_user_keyring(current_cred());
 
        lockdep_off();
-       rc = key_link(current_cred()->session_keyring, key);
+       rc = key_link(keyring, key);
        lockdep_on();
        if (unlikely(rc)) {
                CERROR("failed to link key %08x to keyring %08x: %d\n",
-                      key->serial,
-                      current_cred()->session_keyring->serial, rc);
+                      key->serial, keyring->serial, rc);
                RETURN(rc);
        }
 
        CDEBUG(D_SEC,
              "key %08x (%p) linked to keyring %08x and instantiated, ctx %p\n",
-              key->serial, key, current_cred()->session_keyring->serial,
-              key_get_payload(key, 0));
+              key->serial, key, keyring->serial, key_get_payload(key, 0));
        RETURN(0);
 }
 
@@ -1580,10 +1651,10 @@ static int gss_kt_match_preparse(struct key_match_data *match_data)
 static
 void gss_kt_destroy(struct key *key)
 {
-        ENTRY;
+       ENTRY;
        LASSERT(!key_get_payload(key, 0));
-        CDEBUG(D_SEC, "destroy key %p\n", key);
-        EXIT;
+       CDEBUG(D_SEC, "destroy key %08x %p\n", key->serial, key);
+       EXIT;
 }
 
 static
index a9e2861..44ad372 100644 (file)
@@ -96,7 +96,7 @@ static int sptlrpc_info_lprocfs_seq_show(struct seq_file *seq, void *v)
        seq_printf(seq, "refcount:      %d\n",
                   atomic_read(&sec->ps_refcount));
        seq_printf(seq, "nctx:  %d\n", atomic_read(&sec->ps_nctx));
-       seq_printf(seq, "gc internal    %lld\n", sec->ps_gc_interval);
+       seq_printf(seq, "gc interval    %lld\n", sec->ps_gc_interval);
        seq_printf(seq, "gc next        %lld\n",
                   sec->ps_gc_interval ?
                   (s64)(sec->ps_gc_next - ktime_get_real_seconds()) : 0ll);
index c497155..cbfce32 100755 (executable)
@@ -322,15 +322,15 @@ test_5() {
        $RUNAS touch $file1 || error "can't touch $file1"
        [ -f $file1 ] || error "$file1 not found"
 
+       # flush context
+       $RUNAS $LFS flushctx $MOUNT || error "can't flush context"
+
        # stop lsvcgssd
        send_sigint $(comma_list $(mdts_nodes)) $LSVCGSSD
        sleep 5
        check_gss_daemon_nodes $(comma_list $(mdts_nodes)) $LSVCGSSD &&
                error "$LSVCGSSD still running"
 
-       # flush context, and touch
-       $RUNAS $LFS flushctx -k -r $MOUNT || error "can't flush context (1)"
-       restore_krb5_cred
        $RUNAS touch $file2 && error "should fail without $LSVCGSSD"
 
        # restart lsvcgssd, expect touch succeed
@@ -338,8 +338,6 @@ test_5() {
        start_gss_daemons $(comma_list $(mdts_nodes)) "$LSVCGSSD -vvv"
        sleep 5
        check_gss_daemon_nodes $(comma_list $(mdts_nodes)) $LSVCGSSD
-       $RUNAS $LFS flushctx -k -r $MOUNT || error "can't flush context (2)"
-       restore_krb5_cred
        $RUNAS touch $file2 || error "should not fail now"
        [ -f $file2 ] || error "$file2 not found"
 }
index 11f12f6..aef75fc 100644 (file)
@@ -554,91 +554,154 @@ void lgssc_fini_nego_data(struct lgss_nego_data *lnd)
         }
 }
 
-static
-int error_kernel_key(key_serial_t keyid, int rpc_error, int gss_error)
+static int fork_and_switch_id(int uid, pid_t *child)
 {
-        int      seqwin = 0;
-        char     buf[32];
-        char    *p, *end;
+       int status, rc = 0;
+
+       *child = fork();
+       if (*child == -1) {
+               logmsg(LL_ERR, "cannot fork child for user %u: %s\n",
+                      uid, strerror(errno));
+               rc = errno;
+       } else if (*child == 0) {
+               /* switch identity */
+               rc = switch_identity(uid);
+               if (rc)
+                       rc = errno;
+       } else {
+               if (wait(&status) < 0) {
+                       rc = errno;
+                       logmsg(LL_ERR, "child %d failed: %s\n",
+                              *child, strerror(rc));
+               } else {
+                       rc = WEXITSTATUS(status);
+                       if (rc)
+                               logmsg(LL_ERR, "child %d terminated with %d\n",
+                                      *child, rc);
+               }
+       }
+       return rc;
+}
 
-        logmsg(LL_TRACE, "revoking kernel key %08x\n", keyid);
+static int do_keyctl_update(char *reason, key_serial_t keyid,
+                           const void *payload, size_t plen)
+{
+       while (keyctl_update(keyid, payload, plen)) {
+               if (errno != EAGAIN) {
+                       logmsg(LL_ERR, "%se key %08x: %s\n",
+                              reason, keyid, strerror(errno));
+                       return -1;
+               }
 
-        p = buf;
-        end = buf + sizeof(buf);
+               logmsg(LL_WARN, "key %08x: %sing too soon, try again\n",
+                      keyid, reason);
+               sleep(2);
+       }
 
-        WRITE_BYTES(&p, end, seqwin);
-        WRITE_BYTES(&p, end, rpc_error);
-        WRITE_BYTES(&p, end, gss_error);
+       logmsg(LL_INFO, "key %08x: %sed\n", keyid, reason);
+       return 0;
+}
 
-again:
-        if (keyctl_update(keyid, buf, p - buf)) {
-                if (errno != EAGAIN) {
-                        logmsg(LL_ERR, "revoke key %08x: %s\n",
-                               keyid, strerror(errno));
-                        return -1;
-                }
+static int error_kernel_key(key_serial_t keyid, int rpc_error, int gss_error,
+                           uid_t uid)
+{
+       key_serial_t inst_keyring = KEY_SPEC_SESSION_KEYRING;
+       pid_t child = 1;
+       int seqwin = 0;
+       char *p, *end;
+       char buf[32];
+       int rc;
 
-                logmsg(LL_WARN, "key %08x: revoking too soon, try again\n",
-                       keyid);
-                sleep(2);
-                goto again;
-        }
+       logmsg(LL_TRACE, "revoking kernel key %08x\n", keyid);
 
-        logmsg(LL_INFO, "key %08x: revoked\n", keyid);
-        return 0;
+       /* Only possessor and uid can update the key. So for a user key that is
+        * linked to the user keyring, switch uid/gid in a subprocess to not
+        * change identity in main process.
+        */
+       if (uid) {
+               rc = fork_and_switch_id(uid, &child);
+               if (rc || child)
+                       goto out;
+               inst_keyring = KEY_SPEC_USER_KEYRING;
+       }
+
+       p = buf;
+       end = buf + sizeof(buf);
+
+       WRITE_BYTES(&p, end, seqwin);
+       WRITE_BYTES(&p, end, rpc_error);
+       WRITE_BYTES(&p, end, gss_error);
+
+       rc = do_keyctl_update("revok", keyid, buf, p - buf);
+       if (rc)
+               goto out;
+       rc = keyctl_unlink(keyid, inst_keyring);
+       if (rc)
+               logmsg(LL_ERR, "unlink key %08x from %d: %s\n",
+                      keyid, inst_keyring, strerror(errno));
+       else
+               logmsg(LL_INFO, "key %08x: unlinked from %d\n",
+                      keyid, inst_keyring);
+
+out:
+       if (child == 0)
+               /* job done for child */
+               exit(rc);
+       return rc;
 }
 
-static
-int update_kernel_key(key_serial_t keyid,
-                      struct lgss_nego_data *lnd,
-                      gss_buffer_desc *ctx_token)
+static int update_kernel_key(key_serial_t keyid,
+                            struct lgss_nego_data *lnd,
+                            gss_buffer_desc *ctx_token)
 {
-        char        *buf = NULL, *p = NULL, *end = NULL;
-        unsigned int buf_size = 0;
-        int          rc;
-
-        logmsg(LL_TRACE, "updating kernel key %08x\n", keyid);
-
-        buf_size = sizeof(lnd->lnd_seq_win) +
-                   sizeof(lnd->lnd_rmt_ctx.length) + lnd->lnd_rmt_ctx.length +
-                   sizeof(ctx_token->length) + ctx_token->length;
-        buf = malloc(buf_size);
-        if (buf == NULL) {
-                logmsg(LL_ERR, "key %08x: can't alloc update buf: size %d\n",
-                       keyid, buf_size);
-                return 1;
-        }
+       char *buf = NULL, *p = NULL, *end = NULL;
+       unsigned int buf_size = 0;
+       pid_t child = 1;
+       int uid, rc = 0;
 
-        p = buf;
-        end = buf + buf_size;
-        rc = -1;
-
-        if (WRITE_BYTES(&p, end, lnd->lnd_seq_win))
-                goto out;
-        if (write_buffer(&p, end, &lnd->lnd_rmt_ctx))
-                goto out;
-        if (write_buffer(&p, end, ctx_token))
-                goto out;
-
-again:
-        if (keyctl_update(keyid, buf, p - buf)) {
-                if (errno != EAGAIN) {
-                        logmsg(LL_ERR, "update key %08x: %s\n",
-                               keyid, strerror(errno));
-                        goto out;
-                }
+       logmsg(LL_TRACE, "updating kernel key %08x\n", keyid);
 
-                logmsg(LL_DEBUG, "key %08x: updating too soon, try again\n",
-                       keyid);
-                sleep(2);
-                goto again;
-        }
+       /* Only possessor and uid can update the key. So for a user key that is
+        * linked to the user keyring, switch uid/gid in a subprocess to not
+        * change identity in main process.
+        */
+       uid = lnd->lnd_uid;
+       if (uid) {
+               rc = fork_and_switch_id(uid, &child);
+               if (rc || child)
+                       goto out;
+       }
+
+       buf_size = sizeof(lnd->lnd_seq_win) +
+               sizeof(lnd->lnd_rmt_ctx.length) + lnd->lnd_rmt_ctx.length +
+               sizeof(ctx_token->length) + ctx_token->length;
+       buf = malloc(buf_size);
+       if (buf == NULL) {
+               logmsg(LL_ERR, "key %08x: can't alloc update buf: size %d\n",
+                      keyid, buf_size);
+               rc = ENOMEM;
+               goto out;
+       }
+
+       p = buf;
+       end = buf + buf_size;
+       rc = -1;
+
+       if (WRITE_BYTES(&p, end, lnd->lnd_seq_win))
+               goto out;
+       if (write_buffer(&p, end, &lnd->lnd_rmt_ctx))
+               goto out;
+       if (write_buffer(&p, end, ctx_token))
+               goto out;
+
+       rc = do_keyctl_update("updat", keyid, buf, p - buf);
 
-        rc = 0;
-        logmsg(LL_DEBUG, "key %08x: updated\n", keyid);
 out:
-        free(buf);
-        return rc;
+       free(buf);
+       if (child == 0)
+               /* job done for child */
+               exit(rc);
+       return rc;
 }
 
 static int lgssc_kr_negotiate_krb(key_serial_t keyid, struct lgss_cred *cred,
@@ -653,13 +716,13 @@ static int lgssc_kr_negotiate_krb(key_serial_t keyid, struct lgss_cred *cred,
        if (lgss_get_service_str(&g_service, kup->kup_svc, kup->kup_nid)) {
                logmsg(LL_ERR, "key %08x: failed to construct service "
                       "string\n", keyid);
-               error_kernel_key(keyid, -EACCES, 0);
+               error_kernel_key(keyid, -EACCES, 0, cred->lc_uid);
                goto out_cred;
        }
 
        if (lgss_using_cred(cred)) {
                logmsg(LL_ERR, "key %08x: can't using cred\n", keyid);
-               error_kernel_key(keyid, -EACCES, 0);
+               error_kernel_key(keyid, -EACCES, 0, cred->lc_uid);
                goto out_cred;
        }
 
@@ -668,7 +731,8 @@ retry_nego:
        if (lgssc_init_nego_data(&lnd, kup, cred->lc_mech->lmt_mech_n)) {
                logmsg(LL_ERR, "key %08x: failed to initialize "
                       "negotiation data\n", keyid);
-               error_kernel_key(keyid, lnd.lnd_rpc_err, lnd.lnd_gss_err);
+               error_kernel_key(keyid, lnd.lnd_rpc_err, lnd.lnd_gss_err,
+                                cred->lc_uid);
                goto out_cred;
        }
 
@@ -679,7 +743,8 @@ retry_nego:
                goto retry_nego;
        } else if (rc) {
                logmsg(LL_ERR, "key %08x: failed to negotiation\n", keyid);
-               error_kernel_key(keyid, lnd.lnd_rpc_err, lnd.lnd_gss_err);
+               error_kernel_key(keyid, lnd.lnd_rpc_err, lnd.lnd_gss_err,
+                                cred->lc_uid);
                goto out;
        }
 
@@ -687,7 +752,7 @@ retry_nego:
                                          lnd.lnd_mech);
        if (rc) {
                logmsg(LL_ERR, "key %08x: failed to export context\n", keyid);
-               error_kernel_key(keyid, rc, lnd.lnd_gss_err);
+               error_kernel_key(keyid, rc, lnd.lnd_gss_err, cred->lc_uid);
                goto out;
        }
 
@@ -722,14 +787,14 @@ static int lgssc_kr_negotiate_manual(key_serial_t keyid, struct lgss_cred *cred,
        if (rc) {
                logmsg(LL_ERR, "key %08x: failed to construct service "
                       "string\n", keyid);
-               error_kernel_key(keyid, -EACCES, 0);
+               error_kernel_key(keyid, -EACCES, 0, 0);
                goto out_cred;
        }
 
        rc = lgss_using_cred(cred);
        if (rc) {
                logmsg(LL_ERR, "key %08x: can't use cred\n", keyid);
-               error_kernel_key(keyid, -EACCES, 0);
+               error_kernel_key(keyid, -EACCES, 0, 0);
                goto out_cred;
        }
 
@@ -739,7 +804,7 @@ retry:
        if (rc) {
                logmsg(LL_ERR, "key %08x: failed to initialize "
                       "negotiation data\n", keyid);
-               error_kernel_key(keyid, lnd.lnd_rpc_err, lnd.lnd_gss_err);
+               error_kernel_key(keyid, lnd.lnd_rpc_err, lnd.lnd_gss_err, 0);
                goto out_cred;
        }
 
@@ -755,7 +820,7 @@ retry:
                goto retry;
        } else if (rc) {
                logmsg(LL_ERR, "key %08x: failed to negotiate\n", keyid);
-               error_kernel_key(keyid, lnd.lnd_rpc_err, lnd.lnd_gss_err);
+               error_kernel_key(keyid, lnd.lnd_rpc_err, lnd.lnd_gss_err, 0);
                goto out;
        }
 
@@ -925,34 +990,69 @@ static int prepare_and_instantiate(struct lgss_cred *cred, key_serial_t keyid,
                                   uint32_t uid)
 {
        key_serial_t inst_keyring;
+       bool prepared = true;
+       pid_t child = 1;
+       int rc = 0;
 
        if (lgss_prepare_cred(cred)) {
                logmsg(LL_ERR, "key %08x: failed to prepare credentials "
                       "for user %d\n", keyid, uid);
-               return 1;
+               /* prepare failed, but still instantiate the key for regular
+                * user, so that it can be used to report the error later
+                * in the process
+                */
+               if (cred->lc_root_flags)
+                       return 1;
+               prepared = false;
        }
 
-       /* pre initialize the key. note the keyring linked to is actually of the
-        * original requesting process, not _this_ upcall process. if it's for
+       /* Pre initialize the key. Note the keyring linked to is actually of the
+        * original requesting process, not _this_ upcall process. If it's for
         * root user, don't link to any keyrings because we want fully control
-        * on it, and share it among all root sessions; otherswise link to
-        * session keyring.
+        * on it, and share it among all root sessions.
+        * Otherswise link to user keyring, which requires switching uid/gid.
+        * Do this in a subprocess because other operations need privileges.
         */
-       if (cred->lc_root_flags != 0)
+       if (cred->lc_root_flags) {
                inst_keyring = 0;
-       else
-               inst_keyring = KEY_SPEC_SESSION_KEYRING;
+       } else {
+               inst_keyring = KEY_SPEC_USER_KEYRING;
+               /* fork to not change identity in main process */
+               rc = fork_and_switch_id(uid, &child);
+               if (rc || child)
+                       goto out;
+       }
 
-       if (keyctl_instantiate(keyid, NULL, 0, inst_keyring)) {
-               logmsg(LL_ERR, "instantiate key %08x in keyring id %d: %s\n",
-                      keyid, inst_keyring, strerror(errno));
-               return 1;
+       /* if dealing with a user key, grant user write permission,
+        * it will be required for key update
+        */
+       if (child == 0) {
+               key_perm_t perm;
+
+               perm = KEY_POS_VIEW | KEY_POS_WRITE | KEY_POS_SEARCH |
+                       KEY_POS_LINK | KEY_POS_SETATTR |
+                       KEY_USR_VIEW | KEY_USR_WRITE;
+               if (keyctl_setperm(keyid, perm))
+                       logmsg(LL_ERR, "setperm %08x on key %08x: %s\n",
+                              perm, keyid, strerror(errno));
        }
 
-       logmsg(LL_TRACE, "instantiated kernel key %08x in keyring id %d\n",
-              keyid, inst_keyring);
+       rc = keyctl_instantiate(keyid, NULL, 0, inst_keyring);
+       if (rc) {
+               rc = errno;
+               logmsg(LL_ERR, "instantiate key %08x in keyring id %d: %s\n",
+                      keyid, inst_keyring, strerror(rc));
+       } else {
+               logmsg(LL_TRACE,
+                      "instantiated kernel key %08x in keyring id %d\n",
+                      keyid, inst_keyring);
+       }
 
-       return 0;
+out:
+       if (child == 0)
+               /* job done for child */
+               exit(rc);
+       return prepared ? rc : 1;
 }
 
 /****************************************
@@ -1274,9 +1374,11 @@ out_pipe:
                else
                        logmsg(LL_TRACE, "stick with current namespace\n");
 
+               /* In case of prepare error, a key will be instantiated
+                * all the same. But then we will have to error this key
+                * instead of doing normal gss negotiation.
+                */
                rc = prepare_and_instantiate(cred, keyid, uparam.kup_uid);
-               if (rc != 0)
-                       goto out_reg;
 
                /*
                 * fork a child to do the real gss negotiation
@@ -1288,12 +1390,16 @@ out_pipe:
                        rc = 1;
                        goto out_reg;
                } else if (child == 0) {
-                       rc = lgssc_kr_negotiate(keyid, cred, &uparam,
-                                               req_fd, reply_fd);
+                       if (rc)
+                               rc = error_kernel_key(keyid, -ENOKEY, 0,
+                                                     cred->lc_uid);
+                       else
+                               rc = lgssc_kr_negotiate(keyid, cred, &uparam,
+                                                       req_fd, reply_fd);
                        goto out_reg;
                } else {
                        logmsg(LL_TRACE, "forked child %d\n", child);
-                       return rc;
+                       return 0;
                }
 
 out_reg:
index a8ee720..bf58d17 100644 (file)
 #include <sys/param.h>
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <grp.h>
-#include <pwd.h>
 #include <sys/mman.h>
 #include <sys/wait.h>
 #include <sys/utsname.h>
@@ -322,48 +320,6 @@ static int get_root_tgt_ccname(krb5_context ctx, char *ccname, int size)
        return lgss_krb5_get_default_ccache_name(ctx, ccname, size);
 }
 
-static int switch_identity(uid_t uid)
-{
-       struct passwd *pw;
-       int rc;
-
-       /* drop list of supp groups */
-       rc = setgroups(0, NULL);
-       if (rc == -1) {
-               logmsg(LL_ERR, "cannot drop list of supp groups: %s\n",
-                      strerror(errno));
-               rc = -errno;
-               goto end_switch;
-       }
-
-       pw = getpwuid(uid);
-       if (!pw) {
-               logmsg(LL_ERR, "cannot get pw entry for %u: %s\n",
-                      uid, strerror(errno));
-               rc = -errno;
-               goto end_switch;
-       }
-
-       rc = setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid);
-       if (rc == -1) {
-               logmsg(LL_ERR, "cannot set real gid to %u: %s\n",
-                      pw->pw_gid, strerror(errno));
-               rc = -errno;
-               goto end_switch;
-       }
-
-       rc = setresuid(uid, uid, uid);
-       if (rc == -1) {
-               logmsg(LL_ERR, "cannot set real uid to %u: %s\n",
-                      uid, strerror(errno));
-               rc = -errno;
-               goto end_switch;
-       }
-
-end_switch:
-       return rc;
-}
-
 static int acquire_user_cred_and_check(char *ccname)
 {
        gss_OID mech = (gss_OID)&krb5oid;
index b29151d..b3439b6 100644 (file)
@@ -84,6 +84,8 @@
 #include <fcntl.h>
 #include <stdarg.h>
 #include <syslog.h>
+#include <grp.h>
+#include <pwd.h>
 #include <gssapi/gssapi.h>
 #if defined(HAVE_KRB5) && !defined(GSS_C_NT_HOSTBASED_SERVICE)
 #include <gssapi/gssapi_generic.h>
@@ -535,3 +537,45 @@ int lgss_get_service_str(char **string, uint32_t lsvc, uint64_t tgt_nid)
        logmsg(LL_DEBUG, "constructed service string: %s\n", *string);
        return 0;
 }
+
+int switch_identity(uid_t uid)
+{
+       struct passwd *pw;
+       int rc;
+
+       /* drop list of supp groups */
+       rc = setgroups(0, NULL);
+       if (rc == -1) {
+               logmsg(LL_ERR, "cannot drop list of supp groups: %s\n",
+                      strerror(errno));
+               rc = -errno;
+               goto end_switch;
+       }
+
+       pw = getpwuid(uid);
+       if (!pw) {
+               logmsg(LL_ERR, "cannot get pw entry for %u: %s\n",
+                      uid, strerror(errno));
+               rc = -errno;
+               goto end_switch;
+       }
+
+       rc = setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid);
+       if (rc == -1) {
+               logmsg(LL_ERR, "cannot set real gid to %u: %s\n",
+                      pw->pw_gid, strerror(errno));
+               rc = -errno;
+               goto end_switch;
+       }
+
+       rc = setresuid(uid, uid, uid);
+       if (rc == -1) {
+               logmsg(LL_ERR, "cannot set real uid to %u: %s\n",
+                      uid, strerror(errno));
+               rc = -errno;
+               goto end_switch;
+       }
+
+end_switch:
+       return rc;
+}
index 5e4f23b..86f3aca 100644 (file)
@@ -179,6 +179,7 @@ int lgss_validate_cred(struct lgss_cred *cred, gss_buffer_desc *token,
 void lgss_fini(struct lgss_cred *cred);
 
 int lgss_get_service_str(char **string, uint32_t lsvc, uint64_t tgt_nid);
+int switch_identity(uid_t uid);
 
 static inline
 int gss_OID_equal(gss_OID_desc *oid1, gss_OID_desc *oid2)