Viewing: lgss_keyring.c
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
* Use is subject to license terms.
*
* Copyright (c) 2011, Intel Corporation.
*/
/*
* This file is part of Lustre, http://www.lustre.org/
*
* lustre/utils/gss/lgss_keyring.c
*
* user-space upcall to create GSS context, using keyring interface to kernel
*
* Author: Eric Mei <ericm@clusterfs.com>
*/
#include <sched.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <inttypes.h>
#include <string.h>
#include <errno.h>
#include <pwd.h>
#include <keyutils.h>
#include <gssapi/gssapi.h>
#include <sys/wait.h>
#include <getopt.h>
#include <linux/lustre/lustre_user.h>
#include <libcfs/util/param.h>
#include <libcfs/util/string.h>
#include "lsupport.h"
#include "lgss_utils.h"
#include "lgss_krb5_utils.h"
#include "write_bytes.h"
#include "context.h"
/*
* gss target string of lustre service we are negotiating for
*/
static char *g_service = NULL;
/*
* all data about negotiation
*/
struct lgss_nego_data {
uint32_t lnd_established:1;
int lnd_secid;
uint32_t lnd_uid;
uint32_t lnd_lsvc;
char *lnd_uuid;
gss_OID lnd_mech; /* mech OID */
gss_name_t lnd_svc_name; /* service name */
unsigned int lnd_req_flags; /* request flags */
gss_cred_id_t lnd_cred; /* credential */
gss_ctx_id_t lnd_ctx; /* session context */
gss_buffer_desc lnd_rmt_ctx; /* remote handle of context */
gss_buffer_desc lnd_ctx_token; /* context token for kernel */
uint32_t lnd_seq_win; /* sequence window */
int lnd_rpc_err;
int lnd_gss_err;
};
/*
* context creation response
*/
struct lgss_init_res {
gss_buffer_desc gr_ctx; /* context handle */
unsigned int gr_major; /* major status */
unsigned int gr_minor; /* minor status */
unsigned int gr_win; /* sequence window */
gss_buffer_desc gr_token; /* token */
};
struct keyring_upcall_param {
uint32_t kup_ver;
uint32_t kup_secid;
uint32_t kup_uid;
uint32_t kup_fsuid;
uint32_t kup_gid;
uint32_t kup_fsgid;
uint32_t kup_svc;
uint64_t kup_nid;
uint64_t kup_selfnid;
char kup_svc_type;
char kup_tgt[64];
char kup_mech[16];
unsigned int kup_is_root:1,
kup_is_mdt:1,
kup_is_ost:1;
uint32_t kup_pid;
char kup_cluuid[UUID_MAX];
};
/****************************************
* child process: gss negotiation *
****************************************/
static int send_to(int fd, const void *buf, size_t size)
{
ssize_t sz;
sz = write(fd, buf, size);
if (sz == -1) {
logmsg(LL_ERR, "cannot send to GSS process: %s\n",
strerror(errno));
return -errno;
}
if (sz < size) {
logmsg(LL_ERR, "short write sending to GSS process: %d/%d\n",
(int)sz, (int)size);
return -EPROTO;
}
return 0;
}
static int receive_from(int fd, void *buf, size_t size)
{
ssize_t sz;
sz = read(fd, buf, size);
if (sz == -1) {
logmsg(LL_ERR, "cannot receive from GSS process: %s\n",
strerror(errno));
return -errno;
}
if (sz < size) {
logmsg(LL_ERR, "short read receiving from GSS process: %d/%d\n",
(int)sz, (int)size);
return -EPROTO;
}
return 0;
}
static int gss_do_ioctl(struct lgssd_ioctl_param *param, __s64 *status)
{
int fd, ret;
glob_t path;
int rc;
/* switch to root in order to proceed to ioctls */
if (param->uid && switch_identity(0)) {
rc = -EACCES;
goto out_params;
}
rc = cfs_get_param_paths(&path, "sptlrpc/gss/init_channel");
if (rc != 0)
return rc;
logmsg(LL_TRACE, "to open %s\n", path.gl_pathv[0]);
fd = open(path.gl_pathv[0], O_WRONLY);
if (fd < 0) {
logmsg(LL_ERR, "can't open %s\n", path.gl_pathv[0]);
rc = -EACCES;
goto out_params;
}
logmsg(LL_TRACE, "to down-write\n");
*status = 0;
param->status = status;
ret = write(fd, param, sizeof(*param));
close(fd);
if (ret != sizeof(*param)) {
logmsg(LL_ERR, "lustre ioctl err: %s\n", strerror(errno));
rc = -EACCES;
}
out_params:
cfs_free_param_data(&path);
/* switch back to user */
if (param->uid && switch_identity(param->uid))
rc = -EACCES;
return rc;
}
static int do_nego_rpc(struct lgss_nego_data *lnd,
gss_buffer_desc *gss_token,
struct lgss_init_res *gr,
int req_fd[2], int reply_fd[2])
{
struct lgssd_ioctl_param param;
struct passwd *pw;
int res;
char outbuf[8192] = { 0 };
unsigned int *p;
__s64 status;
int rc = 0;
logmsg(LL_TRACE, "start negotiation rpc\n");
pw = getpwuid(lnd->lnd_uid);
if (!pw) {
logmsg(LL_ERR, "no uid %u in local user database\n",
lnd->lnd_uid);
return -EACCES;
}
param.version = GSSD_INTERFACE_VERSION;
param.secid = lnd->lnd_secid;
param.uuid = lnd->lnd_uuid;
param.lustre_svc = lnd->lnd_lsvc;
param.uid = lnd->lnd_uid;
param.gid = pw->pw_gid;
param.send_token_size = gss_token->length;
param.send_token = (char *) gss_token->value;
if (req_fd[0] == -1 && reply_fd[0] == -1) {
/* we can do the ioctl directly */
param.reply_buf_size = sizeof(outbuf);
param.reply_buf = outbuf;
rc = gss_do_ioctl(¶m, &status);
if (rc != 0)
return rc;
} else {
/* looks like we are running in a container,
* so we cannot do the ioctl ourselves: delegate to
* parent process running directly on host */
/* send ioctl buffer to parent */
rc = send_to(req_fd[1], ¶m, sizeof(param));
if (rc != 0)
return rc;
/* send gss token to parent */
rc = send_to(req_fd[1], gss_token->value, gss_token->length);
if (rc != 0)
return rc;
/* read ioctl status from parent */
rc = receive_from(reply_fd[0], &status, sizeof(status));
if (rc != 0)
return rc;
if (status == 0) {
/* read reply buffer from parent */
rc = receive_from(reply_fd[0], outbuf, sizeof(outbuf));
if (rc != 0)
return rc;
}
}
logmsg(LL_TRACE, "do_nego_rpc: to parse reply\n");
if (status) {
logmsg(LL_ERR, "status: %ld (%s)\n",
(long int)status, strerror((int)(-status)));
return status;
}
p = (unsigned int *)outbuf;
res = *p++;
gr->gr_major = *p++;
gr->gr_minor = *p++;
gr->gr_win = *p++;
gr->gr_ctx.length = *p++;
gr->gr_ctx.value = malloc(gr->gr_ctx.length);
if (gr->gr_ctx.value == NULL)
return -ENOMEM;
memcpy(gr->gr_ctx.value, p, gr->gr_ctx.length);
p += (((gr->gr_ctx.length + 3) & ~3) / 4);
gr->gr_token.length = *p++;
gr->gr_token.value = malloc(gr->gr_token.length);
if (gr->gr_token.value == NULL) {
free(gr->gr_ctx.value);
return -ENOMEM;
}
memcpy(gr->gr_token.value, p, gr->gr_token.length);
p += (((gr->gr_token.length + 3) & ~3) / 4);
logmsg(LL_DEBUG, "do_nego_rpc: receive handle len %zu, token len %zu, "
"res %d\n", gr->gr_ctx.length, gr->gr_token.length, res);
return rc;
}
/* This is used by incomplete GSSAPI implementations that can't use
* gss_init_sec_context and will parse the token themselves (gssnull and sk).
* Callers should have cred->lc_mech_token pointing to a gss_buffer_desc
* token to send to the peer as part of the SEC_CTX_INIT operation. The return
* RPC's token with be in gr.gr_token which is validated using
* lgss_validate_cred. */
static int lgssc_negotiation_manual(struct lgss_nego_data *lnd,
struct lgss_cred *cred,
int req_fd[2], int reply_fd[2])
{
struct lgss_init_res gr;
OM_uint32 min_stat;
int rc;
logmsg(LL_TRACE, "starting gss negotation\n");
memset(&gr, 0, sizeof(gr));
lnd->lnd_rpc_err = do_nego_rpc(lnd, &cred->lc_mech_token, &gr,
req_fd, reply_fd);
if (lnd->lnd_rpc_err) {
logmsg(LL_ERR, "negotiation rpc error %d\n", lnd->lnd_rpc_err);
rc = lnd->lnd_rpc_err;
goto out_error;
}
if (gr.gr_major == GSS_S_CONTINUE_NEEDED) {
rc = -EAGAIN;
goto out_error;
} else if (gr.gr_major != GSS_S_COMPLETE) {
lnd->lnd_gss_err = gr.gr_major;
logmsg(LL_ERR, "negotiation gss error %x\n", lnd->lnd_gss_err);
rc = -ENOTCONN;
goto out_error;
}
if (gr.gr_ctx.length == 0 || gr.gr_token.length == 0) {
logmsg(LL_ERR, "zero length context or token received\n");
rc = -EINVAL;
goto out_error;
}
rc = lgss_validate_cred(cred, &gr.gr_token, &lnd->lnd_ctx_token);
if (rc) {
logmsg(LL_ERR, "peer token failed validation\n");
goto out_error;
}
lnd->lnd_established = 1;
lnd->lnd_seq_win = gr.gr_win;
lnd->lnd_rmt_ctx = gr.gr_ctx;
if (gr.gr_token.length != 0)
gss_release_buffer(&min_stat, &gr.gr_token);
logmsg(LL_DEBUG, "successfully negotiated a context\n");
return 0;
out_error:
if (gr.gr_ctx.length != 0)
gss_release_buffer(&min_stat, &gr.gr_ctx);
if (gr.gr_token.length != 0)
gss_release_buffer(&min_stat, &gr.gr_token);
return rc;
}
/*
* if return error, the lnd_rpc_err or lnd_gss_err is set.
*/
static int lgssc_negotiation(struct lgss_nego_data *lnd, int req_fd[2],
int reply_fd[2])
{
struct lgss_init_res gr;
gss_buffer_desc *recv_tokenp, send_token;
OM_uint32 maj_stat, min_stat, ret_flags;
logmsg(LL_TRACE, "start gss negotiation\n");
/* GSS context establishment loop. */
memset(&gr, 0, sizeof(gr));
recv_tokenp = GSS_C_NO_BUFFER;
for (;;) {
maj_stat = gss_init_sec_context(&min_stat,
lnd->lnd_cred,
&lnd->lnd_ctx,
lnd->lnd_svc_name,
lnd->lnd_mech,
lnd->lnd_req_flags,
0, /* time req */
NULL, /* channel */
recv_tokenp,
NULL, /* used mech */
&send_token,
&ret_flags,
NULL); /* time rec */
logmsg_gss(LL_TRACE, lnd->lnd_mech, maj_stat, min_stat,
"gss_init_sec_context");
logmsg(LL_TRACE, "send_token:\n");
log_hexl(LL_TRACE, send_token.value, send_token.length);
if (recv_tokenp != GSS_C_NO_BUFFER) {
gss_release_buffer(&min_stat, &gr.gr_token);
recv_tokenp = GSS_C_NO_BUFFER;
}
if (maj_stat != GSS_S_COMPLETE &&
maj_stat != GSS_S_CONTINUE_NEEDED) {
lnd->lnd_gss_err = maj_stat;
logmsg_gss(LL_ERR, lnd->lnd_mech, maj_stat, min_stat,
"failed init context");
break;
}
if (send_token.length != 0) {
memset(&gr, 0, sizeof(gr));
lnd->lnd_rpc_err = do_nego_rpc(lnd, &send_token, &gr,
req_fd, reply_fd);
gss_release_buffer(&min_stat, &send_token);
if (lnd->lnd_rpc_err) {
logmsg(LL_ERR, "negotiation rpc error: %d\n",
lnd->lnd_rpc_err);
return lnd->lnd_rpc_err;
}
if (gr.gr_major != GSS_S_COMPLETE &&
gr.gr_major != GSS_S_CONTINUE_NEEDED) {
lnd->lnd_gss_err = gr.gr_major;
logmsg(LL_ERR, "negotiation gss error %x\n",
lnd->lnd_gss_err);
return -ENOTCONN;
}
if (gr.gr_ctx.length != 0) {
if (lnd->lnd_rmt_ctx.value)
gss_release_buffer(&min_stat,
&lnd->lnd_rmt_ctx);
lnd->lnd_rmt_ctx = gr.gr_ctx;
}
if (gr.gr_token.length != 0) {
if (maj_stat != GSS_S_CONTINUE_NEEDED)
break;
recv_tokenp = &gr.gr_token;
}
}
/* GSS_S_COMPLETE => check gss header verifier,
* usually checked in gss_validate
*/
if (maj_stat == GSS_S_COMPLETE) {
lnd->lnd_established = 1;
lnd->lnd_seq_win = gr.gr_win;
break;
}
}
/* End context negotiation loop. */
if (!lnd->lnd_established) {
if (gr.gr_token.length != 0)
gss_release_buffer(&min_stat, &gr.gr_token);
if (lnd->lnd_gss_err == GSS_S_COMPLETE)
lnd->lnd_rpc_err = -EACCES;
logmsg(LL_ERR, "context negotiation failed\n");
return -1;
}
logmsg(LL_DEBUG, "successfully negotiated a context\n");
return 0;
}
/*
* if return error, the lnd_rpc_err or lnd_gss_err is set.
*/
static int lgssc_init_nego_data(struct lgss_nego_data *lnd,
struct keyring_upcall_param *kup,
enum lgss_mech mech)
{
gss_buffer_desc sname;
OM_uint32 maj_stat, min_stat;
memset(lnd, 0, sizeof(*lnd));
lnd->lnd_secid = kup->kup_secid;
lnd->lnd_uid = kup->kup_uid;
lnd->lnd_lsvc = kup->kup_svc | mech << LUSTRE_GSS_MECH_SHIFT;
lnd->lnd_uuid = kup->kup_tgt;
lnd->lnd_established = 0;
lnd->lnd_svc_name = GSS_C_NO_NAME;
lnd->lnd_cred = GSS_C_NO_CREDENTIAL;
lnd->lnd_ctx = GSS_C_NO_CONTEXT;
lnd->lnd_rmt_ctx = (gss_buffer_desc) GSS_C_EMPTY_BUFFER;
lnd->lnd_seq_win = 0;
switch (mech) {
case LGSS_MECH_KRB5:
lnd->lnd_mech = (gss_OID)&krb5oid;
lnd->lnd_req_flags = GSS_C_MUTUAL_FLAG;
break;
case LGSS_MECH_NULL:
lnd->lnd_mech = (gss_OID)&nulloid;
break;
#ifdef HAVE_OPENSSL_SSK
case LGSS_MECH_SK:
lnd->lnd_mech = (gss_OID)&skoid;
lnd->lnd_req_flags = GSS_C_MUTUAL_FLAG;
break;
#endif
default:
logmsg(LL_ERR, "invalid mech: %d\n", mech);
lnd->lnd_rpc_err = -EACCES;
return -1;
}
sname.value = g_service;
sname.length = strlen(g_service);
maj_stat = gss_import_name(&min_stat, &sname,
(gss_OID) GSS_C_NT_HOSTBASED_SERVICE,
&lnd->lnd_svc_name);
if (maj_stat != GSS_S_COMPLETE) {
logmsg_gss(LL_ERR, lnd->lnd_mech, maj_stat, min_stat,
"can't import svc name");
lnd->lnd_gss_err = maj_stat;
return -1;
}
return 0;
}
static void lgssc_fini_nego_data(struct lgss_nego_data *lnd)
{
OM_uint32 maj_stat, min_stat;
if (lnd->lnd_svc_name != GSS_C_NO_NAME) {
maj_stat = gss_release_name(&min_stat, &lnd->lnd_svc_name);
if (maj_stat != GSS_S_COMPLETE)
logmsg_gss(LL_ERR, lnd->lnd_mech, maj_stat, min_stat,
"can't release service name");
}
if (lnd->lnd_cred != GSS_C_NO_CREDENTIAL) {
maj_stat = gss_release_cred(&min_stat, &lnd->lnd_cred);
if (maj_stat != GSS_S_COMPLETE)
logmsg_gss(LL_ERR, lnd->lnd_mech, maj_stat, min_stat,
"can't release credential");
}
}
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;
}
logmsg(LL_INFO, "key %08x: %sing too soon, try again\n",
keyid, reason);
sleep(1);
}
logmsg(LL_INFO, "key %08x: %sed\n", keyid, reason);
return 0;
}
static int error_kernel_key(key_serial_t keyid, int rpc_error, int gss_error,
uid_t uid)
{
key_serial_t inst_keyring;
int seqwin = 0;
char *p, *end;
char buf[32];
int rc, rc2;
logmsg(LL_TRACE, "revoking kernel key %08x\n", keyid);
if (uid)
inst_keyring = KEY_SPEC_USER_KEYRING;
else
inst_keyring = KEY_SPEC_SESSION_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);
/* no matter if revoking key was successful or not, always try unlink */
rc2 = keyctl_unlink(keyid, inst_keyring);
if (rc2) {
if (rc2 != ENOENT)
logmsg(LL_ERR, "unlink key %08x from %d: %s\n",
keyid, inst_keyring, strerror(errno));
if (!rc)
rc = rc2;
} else {
logmsg(LL_INFO, "key %08x: unlinked from %d\n",
keyid, inst_keyring);
}
return rc;
}
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 = 0;
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);
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);
out:
free(buf);
return rc;
}
static int lgssc_kr_negotiate_krb(key_serial_t keyid, struct lgss_cred *cred,
struct keyring_upcall_param *kup,
int req_fd[2], int reply_fd[2])
{
struct lgss_nego_data lnd;
OM_uint32 min_stat;
int rc = -1;
bool redo = true;
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, 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, cred->lc_uid);
goto out_cred;
}
retry_nego:
memset(&lnd, 0, sizeof(lnd));
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,
cred->lc_uid);
goto out_cred;
}
rc = lgssc_negotiation(&lnd, req_fd, reply_fd);
if (rc == -EAGAIN || (rc == -ETIMEDOUT && redo)) {
logmsg(LL_ERR, "Failed negotiation must retry\n");
redo = false;
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,
cred->lc_uid);
goto out;
}
rc = serialize_context_for_kernel(&lnd.lnd_ctx, &lnd.lnd_ctx_token,
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, cred->lc_uid);
goto out;
}
rc = update_kernel_key(keyid, &lnd, &lnd.lnd_ctx_token);
if (rc)
goto out;
rc = 0;
logmsg(LL_INFO, "key %08x for user %u is updated OK!\n",
keyid, kup->kup_uid);
out:
if (lnd.lnd_ctx_token.length != 0)
(void)gss_release_buffer(&min_stat, &lnd.lnd_ctx_token);
lgssc_fini_nego_data(&lnd);
out_cred:
lgss_release_cred(cred);
return rc;
}
static int lgssc_kr_negotiate_manual(key_serial_t keyid, struct lgss_cred *cred,
struct keyring_upcall_param *kup,
int req_fd[2], int reply_fd[2])
{
struct lgss_nego_data lnd;
OM_uint32 min_stat;
int rc;
bool redo = true;
rc = lgss_get_service_str(&g_service, kup->kup_svc, kup->kup_nid);
if (rc) {
logmsg(LL_ERR, "key %08x: failed to construct service string\n",
keyid);
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, 0);
goto out_cred;
}
retry:
memset(&lnd, 0, sizeof(lnd));
rc = lgssc_init_nego_data(&lnd, kup, cred->lc_mech->lmt_mech_n);
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, 0);
goto out_cred;
}
/*
* Handles the negotiation but then calls lgss_validate to make sure
* the token is valid. It also populates the lnd_ctx_token for the
* update to the kernel key
*/
rc = lgssc_negotiation_manual(&lnd, cred, req_fd, reply_fd);
if (rc == -EAGAIN || (rc == -ETIMEDOUT && redo)) {
logmsg(LL_ERR, "Failed negotiation must retry\n");
redo = false;
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, 0);
goto out;
}
rc = update_kernel_key(keyid, &lnd, &lnd.lnd_ctx_token);
if (rc)
goto out;
logmsg(LL_INFO, "key %08x for user %u is updated OK!\n",
keyid, kup->kup_uid);
out:
if (lnd.lnd_ctx_token.length != 0)
gss_release_buffer(&min_stat, &lnd.lnd_ctx_token);
lgssc_fini_nego_data(&lnd);
out_cred:
lgss_release_cred(cred);
return rc;
}
/*
* note we inherited assumed authority from parent process
*/
static int lgssc_kr_negotiate(key_serial_t keyid, struct lgss_cred *cred,
struct keyring_upcall_param *kup,
int req_fd[2], int reply_fd[2])
{
int rc;
logmsg(LL_TRACE, "child start on behalf of key %08x: "
"cred %p, uid %u, svc %u, nid %"PRIx64", uids: %u:%u/%u:%u\n",
keyid, cred, cred->lc_uid, cred->lc_tgt_svc, cred->lc_tgt_nid,
kup->kup_uid, kup->kup_gid, kup->kup_fsuid, kup->kup_fsgid);
switch (cred->lc_mech->lmt_mech_n) {
case LGSS_MECH_NULL:
case LGSS_MECH_SK:
rc = lgssc_kr_negotiate_manual(keyid, cred, kup,
req_fd, reply_fd);
break;
case LGSS_MECH_KRB5:
default:
rc = lgssc_kr_negotiate_krb(keyid, cred, kup, req_fd, reply_fd);
break;
}
return rc;
}
/*
* call out info format: s[:s]...
* [0]: secid (uint)
* [1]: mech_name (string)
* [2]: uid (uint)
* [3]: gid (uint)
* [4]: flags (string) FMT: r-root; m-mdt; o-ost
* [5]: svc type (char)
* [6]: lustre_svc (int)
* [7]: target_nid (uint64)
* [8]: target_uuid (string)
* [9]: self_nid (uint64)
* [10]: pid (uint)
* [11]: client uuid (string)
*/
static int parse_callout_info(const char *coinfo,
struct keyring_upcall_param *uparam)
{
const int nargs = 12;
const int nargs_min = 11; /* for compatibility with older kernel code */
char buf[1024];
char *string = buf;
int length, i;
char *data[nargs];
char *pos;
length = strlen(coinfo) + 1;
if (length > 1024) {
logmsg(LL_ERR, "coinfo too long\n");
return -1;
}
memcpy(buf, coinfo, length);
for (i = 0; i < nargs; i++) {
data[i] = string;
pos = strchr(string, ':');
if (!pos) {
if (i >= nargs_min - 1) {
i++;
break;
}
logmsg(LL_ERR,
"short components, need minimum %d, got %d\n",
nargs_min, i + 1);
return -1;
}
*pos = '\0';
string = pos + 1;
}
for (; i < nargs; i++)
data[i] = NULL;
logmsg(LL_TRACE, "components: %s,%s,%s,%s,%s,%c,%s,%s,%s,%s,%s,%s\n",
data[0], data[1], data[2], data[3], data[4], data[5][0],
data[6], data[7], data[8], data[9], data[10],
data[11] ?: "<unset>");
uparam->kup_secid = strtol(data[0], NULL, 0);
snprintf(uparam->kup_mech, sizeof(uparam->kup_mech), "%s", data[1]);
uparam->kup_uid = strtol(data[2], NULL, 0);
uparam->kup_gid = strtol(data[3], NULL, 0);
if (strchr(data[4], 'r'))
uparam->kup_is_root = 1;
if (strchr(data[4], 'm'))
uparam->kup_is_mdt = 1;
if (strchr(data[4], 'o'))
uparam->kup_is_ost = 1;
uparam->kup_svc_type = data[5][0];
uparam->kup_svc = strtol(data[6], NULL, 0);
uparam->kup_nid = strtoll(data[7], NULL, 0);
snprintf(uparam->kup_tgt, sizeof(uparam->kup_tgt), "%s", data[8]);
uparam->kup_selfnid = strtoll(data[9], NULL, 0);
uparam->kup_pid = strtol(data[10], NULL, 0);
if (data[11])
snprintf(uparam->kup_cluuid, sizeof(uparam->kup_cluuid), "%s",
data[11]);
else
uparam->kup_cluuid[0] = '\0';
logmsg(LL_DEBUG, "parse call out info: secid %d, mech %s, ugid %u:%u, "
"is_root %d, is_mdt %d, is_ost %d, svc type %c, svc %d, "
"nid 0x%"PRIx64", tgt %s, self nid 0x%"PRIx64", pid %d, "
"uuid %s\n",
uparam->kup_secid, uparam->kup_mech,
uparam->kup_uid, uparam->kup_gid,
uparam->kup_is_root, uparam->kup_is_mdt, uparam->kup_is_ost,
uparam->kup_svc_type, uparam->kup_svc, uparam->kup_nid,
uparam->kup_tgt, uparam->kup_selfnid, uparam->kup_pid,
uparam->kup_cluuid[0] != '\0' ? uparam->kup_cluuid : "<unset>");
return 0;
}
static void set_log_level()
{
unsigned int level;
glob_t path;
FILE *file;
if (cfs_get_param_paths(&path,
"sptlrpc/gss/lgss_keyring/debug_level") != 0)
return;
file = fopen(path.gl_pathv[0], "r");
if (file == NULL) {
cfs_free_param_data(&path);
return;
}
if (fscanf(file, "%u", &level) != 1)
goto out;
if (level >= LL_MAX)
goto out;
lgss_set_loglevel(level);
out:
cfs_free_param_data(&path);
fclose(file);
}
static int associate_with_ns(char *path)
{
int fd, rc = -1;
fd = open(path, O_RDONLY);
if (fd != -1) {
rc = setns(fd, 0);
close(fd);
}
return rc;
}
static int prepare_and_instantiate(struct lgss_cred *cred, key_serial_t keyid,
uint32_t uid)
{
key_serial_t inst_keyring;
bool prepared = true;
int rc;
if (lgss_prepare_cred(cred)) {
logmsg(LL_ERR, "key %08x: failed to prepare credentials "
"for user %d\n", keyid, uid);
/* 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
* 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 user keyring, which requires switching uid/gid.
* Do this in a subprocess because other operations need privileges.
*/
if (cred->lc_root_flags) {
inst_keyring = 0;
} else {
key_perm_t perm;
inst_keyring = KEY_SPEC_USER_KEYRING;
/* when dealing with a user key, grant user write permission,
* it will be required for key update
*/
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));
}
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 prepared ? rc : 1;
}
/****************************************
* main process *
****************************************/
int main(int argc, char *argv[])
{
struct keyring_upcall_param uparam;
key_serial_t keyid;
key_serial_t sring;
pid_t child;
int req_fd[2] = { -1, -1 };
int reply_fd[2] = { -1, -1 };
struct lgss_mech_type *mech;
struct lgss_cred *cred;
char path[PATH_MAX] = "";
int other_ns = 0;
int rc = 0, opt;
struct stat parent_ns = { .st_ino = 0 };
struct stat caller_ns = { .st_ino = 0 };
static struct option long_opts[] = {
{ .name = "realm", .has_arg = required_argument, .val = 'R'},
{ .name = NULL, } };
set_log_level();
logmsg(LL_TRACE, "start parsing parameters\n");
/* one possible option before upcall parameters: -R REALM */
while ((opt = getopt_long(argc, argv, "R:", long_opts, NULL)) != EOF) {
switch (opt) {
case 'R':
lgss_client_realm = optarg;
break;
default:
logmsg(LL_ERR, "invalid parameter %s\n",
argv[optind - 1]);
return 1;
}
}
if (lgss_client_realm) {
/* shift args to meet expected upcall parameters */
argc -= optind - 1;
argv += optind - 1;
}
/*
* parse & sanity check upcall parameters
* expected to be called with:
* [1]: operation
* [2]: key ID
* [3]: key type
* [4]: key description
* [5]: call out info
* [6]: UID
* [7]: GID
* [8]: thread keyring
* [9]: process keyring
* [10]: session keyring
*/
if (argc != 10 + 1) {
logmsg(LL_ERR, "invalid parameter number %d\n", argc);
return 1;
}
memset(&uparam, 0, sizeof(uparam));
if (strcmp(argv[1], "create") != 0) {
logmsg(LL_ERR,
"invalid OP %s (key %s, desc %s, ugid %s:%s, sring %s, coinfo %s)\n",
argv[1], argv[2], argv[4], argv[6], argv[7], argv[10],
argv[5]);
return 1;
}
if (sscanf(argv[2], "%d", &keyid) != 1) {
logmsg(LL_ERR,
"can't extract KeyID: %s (key %s, desc %s, ugid %s:%s, sring %s, coinfo %s)\n",
argv[2], argv[2], argv[4], argv[6], argv[7], argv[10],
argv[5]);
return 1;
}
logmsg(LL_INFO, "key %08x, desc %s, ugid %s:%s, sring %s, coinfo %s\n",
keyid, argv[4], argv[6], argv[7], argv[10], argv[5]);
if (sscanf(argv[6], "%d", &uparam.kup_fsuid) != 1) {
logmsg(LL_ERR, "can't extract UID: %s\n", argv[6]);
return 1;
}
if (sscanf(argv[7], "%d", &uparam.kup_fsgid) != 1) {
logmsg(LL_ERR, "can't extract GID: %s\n", argv[7]);
return 1;
}
if (sscanf(argv[10], "%d", &sring) != 1) {
logmsg(LL_ERR, "can't extract session keyring: %s\n", argv[10]);
return 1;
}
if (parse_callout_info(argv[5], &uparam)) {
logmsg(LL_ERR, "can't extract callout info: %s\n", argv[5]);
return 1;
}
logmsg(LL_TRACE, "parsing parameters OK\n");
/*
* prepare a cred
*/
mech = lgss_name2mech(uparam.kup_mech);
if (mech == NULL) {
logmsg(LL_ERR, "key %08x: unsupported mech: %s\n",
keyid, uparam.kup_mech);
return 1;
}
if (lgss_mech_initialize(mech)) {
logmsg(LL_ERR, "key %08x: can't initialize mech %s\n",
keyid, mech->lmt_name);
return 1;
}
cred = lgss_create_cred(mech);
if (cred == NULL) {
logmsg(LL_ERR, "key %08x: can't create a new %s cred\n",
keyid, mech->lmt_name);
return 1;
}
cred->lc_uid = uparam.kup_uid;
cred->lc_root_flags |= uparam.kup_is_root ? LGSS_ROOT_CRED_ROOT : 0;
cred->lc_root_flags |= uparam.kup_is_mdt ? LGSS_ROOT_CRED_MDT : 0;
cred->lc_root_flags |= uparam.kup_is_ost ? LGSS_ROOT_CRED_OST : 0;
cred->lc_tgt_nid = uparam.kup_nid;
cred->lc_tgt_svc = uparam.kup_svc;
cred->lc_tgt_uuid = uparam.kup_tgt;
cred->lc_svc_type = uparam.kup_svc_type;
cred->lc_self_nid = uparam.kup_selfnid;
cred->lc_cluuid =
uparam.kup_cluuid[0] != '\0' ? uparam.kup_cluuid : NULL;
/* Is caller in different namespace? */
/* If passed caller's pid is 0, it means we have to stick
* with current namespace.
*/
if (uparam.kup_pid) {
snprintf(path, sizeof(path), "/proc/%d/ns/mnt", getpid());
if (stat(path, &parent_ns)) {
logmsg(LL_DEBUG, "cannot stat %s: %s\n",
path, strerror(errno));
} else {
snprintf(path, sizeof(path), "/proc/%d/ns/mnt",
uparam.kup_pid);
if (stat(path, &caller_ns))
logmsg(LL_DEBUG, "cannot stat %s: %s\n",
path, strerror(errno));
else if (caller_ns.st_ino != parent_ns.st_ino)
other_ns = 1;
}
}
if (!cred->lc_root_flags) {
/* switch to user id for creds handling */
rc = switch_identity(uparam.kup_uid);
if (rc)
return rc;
}
/*
* if caller's namespace is different, fork a child and associate it
* with caller's namespace to do credentials preparation
*/
if (other_ns) {
logmsg(LL_TRACE, "caller's namespace is different\n");
/* use pipes to pass info between child and parent processes */
if (pipe(req_fd) == -1) {
logmsg(LL_ERR, "key %08x: pipe failed: %s\n",
keyid, strerror(errno));
return 1;
}
if (pipe(reply_fd) == -1) {
logmsg(LL_ERR, "key %08x: pipe failed: %s\n",
keyid, strerror(errno));
return 1;
}
child = fork();
if (child == -1) {
logmsg(LL_ERR, "key %08x: can't create child: %s\n",
keyid, strerror(errno));
rc = 1;
goto out_pipe;
} else if (child == 0) {
int rc2;
/* child process: carry out credentials preparation
* in caller's namespace */
close(req_fd[0]); /* close unsed read end */
req_fd[0] = -1;
close(reply_fd[1]); /* close unsed write end */
reply_fd[1] = -1;
if (associate_with_ns(path) != 0) {
logmsg(LL_ERR,
"failed to attach to pid %d namespace: "
"%s\n", uparam.kup_pid, strerror(errno));
rc = 1;
goto out_pipe;
}
logmsg(LL_TRACE, "working in namespace of pid %d\n",
uparam.kup_pid);
rc = prepare_and_instantiate(cred, keyid,
uparam.kup_uid);
/* send to parent the status of credentials preparation
* and key instantiation */
rc2 = send_to(req_fd[1], &rc, sizeof(rc));
rc = (rc == 0 ? rc2 : rc);
if (rc != 0)
goto out_pipe;
/* now do real gss negotiation
* parent main process will not wait for us,
* as it has to be done in the background */
rc = lgssc_kr_negotiate(keyid, cred, &uparam,
req_fd, reply_fd);
goto out_pipe;
} else {
int rc2;
/* parent process: exchange info with child carrying out
* credentials preparation */
close(req_fd[1]); /* close unsed write end */
req_fd[1] = -1;
close(reply_fd[0]); /* close unsed read end */
reply_fd[0] = -1;
/* get status of credentials preparation
* and key instantiation */
rc2 = receive_from(req_fd[0], &rc, sizeof(rc));
if (rc2 != 0 || rc != 0) {
logmsg(LL_ERR, "child failed preparing creds: "
"%s\n",
rc2 != 0 ? strerror(-rc2)
: strerror(rc));
goto out_pipe;
}
/*
* fork a child here to participate in gss negotiation,
* as it has to be done in the background
*/
child = fork();
if (child == -1) {
logmsg(LL_ERR,
"key %08x: can't create child: %s\n",
keyid, strerror(errno));
rc = 1;
goto out_pipe;
} else if (child == 0) {
struct lgssd_ioctl_param param;
char outbuf[8192] = { 0 };
void *gss_token = NULL;
__s64 status;
/* get ioctl buffer from child */
rc = receive_from(req_fd[0], ¶m,
sizeof(param));
if (rc != 0)
goto out_pipe;
gss_token = calloc(1, param.send_token_size);
if (gss_token == NULL)
goto out_pipe;
/* get gss token from child */
rc = receive_from(req_fd[0], gss_token,
param.send_token_size);
if (rc != 0)
goto out_token;
param.send_token = (char *)gss_token;
param.reply_buf_size = sizeof(outbuf);
param.reply_buf = outbuf;
/* do ioctl in place of child process carrying
* out credentials negotiation: as it runs in
* a container, it might not be able to
* perform ioctl */
rc = gss_do_ioctl(¶m, &status);
if (rc != 0)
goto out_token;
/* send ioctl status to child */
rc = send_to(reply_fd[1], &status,
sizeof(status));
if (rc != 0)
goto out_token;
/* send reply buffer to child */
rc = send_to(reply_fd[1], outbuf,
sizeof(outbuf));
if (rc != 0)
goto out_token;
out_token:
free(gss_token);
goto out_pipe;
}
logmsg(LL_TRACE, "forked child %d\n", child);
}
out_pipe:
close(req_fd[0]);
close(req_fd[1]);
close(reply_fd[0]);
close(reply_fd[1]);
lgss_fini(cred);
return rc;
} else {
if (uparam.kup_pid)
logmsg(LL_TRACE, "caller's namespace is the same\n");
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);
/*
* fork a child to do the real gss negotiation
*/
child = fork();
if (child == -1) {
logmsg(LL_ERR, "key %08x: can't create child: %s\n",
keyid, strerror(errno));
rc = 1;
goto out_reg;
} else if (child == 0) {
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 0;
}
out_reg:
lgss_fini(cred);
return rc;
}
}