LWN.net Logo

[PATCH] Secure user authentication using RPCSEC_GSS [7/7]

From:  Trond Myklebust <trond.myklebust@fys.uio.no>
To:  Linus Torvalds <torvalds@transmeta.com>, NFS maillist <nfs@lists.sourceforge.net>,nfsv4-wg@citi.umich.edu
Subject:  [NFS] [PATCH] Secure user authentication using RPCSEC_GSS [7/7]
Date:  Thu, 31 Oct 2002 21:23:07 +0100


Create named pipes in the rpc_pipefs for use by RPCSEC_GSS security flavours.
Provide the upcall/downcall needed in order to communicate with
the userland daemon in order to initiate an RPCSEC_GSS security
context, and have it pass down the resulting user credential.

Cheers,
  Trond

diff -u --recursive --new-file linux-2.5.45-06-auth_upcall/include/linux/sunrpc/auth.h linux-2.5.45-07-auth_upcall2/include/linux/sunrpc/auth.h
--- linux-2.5.45-06-auth_upcall/include/linux/sunrpc/auth.h	2002-10-31 10:22:55.000000000 -0500
+++ linux-2.5.45-07-auth_upcall2/include/linux/sunrpc/auth.h	2002-10-31 10:23:53.000000000 -0500
@@ -115,6 +115,7 @@
 int			rpcauth_unregister(struct rpc_authops *);
 struct rpc_auth *	rpcauth_create(rpc_authflavor_t, struct rpc_clnt *);
 void			rpcauth_destroy(struct rpc_auth *);
+struct rpc_cred *	rpcauth_lookup_credcache(struct rpc_auth *, struct auth_cred *, int);
 struct rpc_cred *	rpcauth_lookupcred(struct rpc_auth *, int);
 struct rpc_cred *	rpcauth_bindcred(struct rpc_task *);
 void			rpcauth_holdcred(struct rpc_task *);
diff -u --recursive --new-file linux-2.5.45-06-auth_upcall/include/linux/sunrpc/auth_gss.h linux-2.5.45-07-auth_upcall2/include/linux/sunrpc/auth_gss.h
--- linux-2.5.45-06-auth_upcall/include/linux/sunrpc/auth_gss.h	2002-10-31 10:22:55.000000000 -0500
+++ linux-2.5.45-07-auth_upcall2/include/linux/sunrpc/auth_gss.h	2002-10-31 10:23:53.000000000 -0500
@@ -71,6 +71,7 @@
  * the wire when communicating with a server. */
 
 struct gss_ctx {
+	atomic_t		count;
 	u32			gc_proc;
 	u32			gc_seq;
 	spinlock_t		gc_seq_lock;
diff -u --recursive --new-file linux-2.5.45-06-auth_upcall/net/sunrpc/auth.c linux-2.5.45-07-auth_upcall2/net/sunrpc/auth.c
--- linux-2.5.45-06-auth_upcall/net/sunrpc/auth.c	2002-10-31 10:22:55.000000000 -0500
+++ linux-2.5.45-07-auth_upcall2/net/sunrpc/auth.c	2002-10-31 10:23:53.000000000 -0500
@@ -181,7 +181,7 @@
 /*
  * Look up a process' credentials in the authentication cache
  */
-static struct rpc_cred *
+struct rpc_cred *
 rpcauth_lookup_credcache(struct rpc_auth *auth, struct auth_cred * acred,
 		int taskflags)
 {
@@ -360,10 +360,7 @@
 int
 rpcauth_uptodatecred(struct rpc_task *task)
 {
-	int retval;
-	spin_lock(&rpc_credcache_lock);
-	retval = !(task->tk_msg.rpc_cred) ||
+	return !(task->tk_msg.rpc_cred) ||
 		(task->tk_msg.rpc_cred->cr_flags & RPCAUTH_CRED_UPTODATE);
-	spin_unlock(&rpc_credcache_lock);
-	return retval;
 }
+
diff -u --recursive --new-file linux-2.5.45-06-auth_upcall/net/sunrpc/auth_gss.c linux-2.5.45-07-auth_upcall2/net/sunrpc/auth_gss.c
--- linux-2.5.45-06-auth_upcall/net/sunrpc/auth_gss.c	2002-10-31 10:22:55.000000000 -0500
+++ linux-2.5.45-07-auth_upcall2/net/sunrpc/auth_gss.c	2002-10-31 13:11:00.000000000 -0500
@@ -50,6 +50,8 @@
 #include <linux/sunrpc/auth.h>
 #include <linux/sunrpc/auth_gss.h>
 #include <linux/sunrpc/gss_err.h>
+#include <linux/sunrpc/rpc_pipe_fs.h>
+#include <asm/uaccess.h>
 
 static struct rpc_authops authgss_ops;
 
@@ -74,6 +76,20 @@
 /* dump the buffer in `emacs-hexl' style */
 #define isprint(c)      ((c > 0x1f) && (c < 0x7f))
 
+static rwlock_t gss_ctx_lock = RW_LOCK_UNLOCKED;
+
+struct gss_auth {
+	struct rpc_auth rpc_auth;
+	struct gss_api_mech *mech;
+	struct list_head upcalls;
+	struct dentry *dentry;
+	char path[48];
+	spinlock_t lock;
+};
+
+static void gss_destroy_ctx(struct gss_ctx *);
+static struct rpc_pipe_ops gss_upcall_ops;
+
 void
 print_hexl(u32 *p, u_int length, u_int offset)
 {
@@ -112,6 +128,296 @@
 	}
 }
 
+static inline struct gss_ctx *
+gss_get_ctx(struct gss_ctx *ctx)
+{
+	atomic_inc(&ctx->count);
+	return ctx;
+}
+
+static inline void
+gss_put_ctx(struct gss_ctx *ctx)
+{
+	if (atomic_dec_and_test(&ctx->count))
+		gss_destroy_ctx(ctx);
+}
+
+static void
+gss_cred_set_ctx(struct rpc_cred *cred, struct gss_ctx *ctx)
+{
+	struct gss_cred *gss_cred = container_of(cred, struct gss_cred, gc_base);
+	struct gss_ctx *old;
+	write_lock(&gss_ctx_lock);
+	old = gss_cred->gc_ctx;
+	gss_cred->gc_ctx = ctx;
+	cred->cr_flags |= RPCAUTH_CRED_UPTODATE;
+	write_unlock(&gss_ctx_lock);
+	if (old)
+		gss_put_ctx(old);
+}
+
+static struct gss_ctx *
+gss_cred_get_ctx(struct rpc_cred *cred)
+{
+	struct gss_cred *gss_cred = container_of(cred, struct gss_cred, gc_base);
+	struct gss_ctx *ctx = NULL;
+
+	read_lock(&gss_ctx_lock);
+	if ((cred->cr_flags & RPCAUTH_CRED_UPTODATE) && gss_cred->gc_ctx)
+		ctx = gss_get_ctx(gss_cred->gc_ctx);
+	read_unlock(&gss_ctx_lock);
+	return ctx;
+}
+
+static inline int
+simple_get_bytes(char **ptr, const char *end, void *res, int len)
+{
+	char *p, *q;
+	p = *ptr;
+	q = p + len;
+	if (q > end || q < p)
+		return -1;
+	memcpy(res, p, len);
+	*ptr = q;
+	return 0;
+}
+
+static inline int
+simple_get_netobj(char **ptr, const char *end, struct xdr_netobj *res)
+{
+	char *p, *q;
+	p = *ptr;
+	if (simple_get_bytes(&p, end, &res->len, sizeof(res->len)))
+		return -1;
+	q = p + res->len;
+	if (q > end || q < p)
+		return -1;
+	res->data = p;
+	*ptr = q;
+	return 0;
+}
+
+static int
+dup_netobj(struct xdr_netobj *source, struct xdr_netobj *dest)
+{
+	dest->len = source->len;
+	if (!(dest->data = rpc_allocate(0, dest->len)))
+		return -1;
+	memcpy(dest->data, source->data, dest->len);
+	return 0;
+}
+
+static int
+gss_parse_init_downcall(struct gss_api_mech *gm, struct xdr_netobj *buf,
+		struct gss_ctx **gc, uid_t *uid)
+{
+	char *end = buf->data + buf->len;
+	char *p = buf->data;
+	struct gss_ctx *ctx;
+	struct xdr_netobj tmp_buf;
+	unsigned int timeout;
+	int err = -EIO;
+
+	if (!(ctx = rpc_allocate(0, sizeof(*ctx)))) {
+		err = -ENOMEM;
+		goto err;
+	}
+	ctx->gc_proc = RPC_GSS_PROC_DATA;
+	ctx->gc_seq = 0;
+	spin_lock_init(&ctx->gc_seq_lock);
+
+	if (simple_get_bytes(&p, end, uid, sizeof(uid)))
+		goto err_free_ctx;
+	/* FIXME: discarded timeout for now */
+	if (simple_get_bytes(&p, end, &timeout, sizeof(timeout)))
+		goto err_free_ctx;
+	if (simple_get_bytes(&p, end, &ctx->gc_win, sizeof(ctx->gc_win)))
+		goto err_free_ctx;
+	if (simple_get_netobj(&p, end, &tmp_buf))
+		goto err_free_ctx;
+	if (!dup_netobj(&tmp_buf, &ctx->gc_wire_ctx)) {
+		err = -ENOMEM;
+		goto err_free_ctx;
+	}
+	if (simple_get_netobj(&p, end, &tmp_buf))
+		goto err_free_wire_ctx;
+	if (p != end)
+		goto err_free_wire_ctx;
+	if (gss_import_sec_context(&tmp_buf, gm, &ctx->gc_client_ctx))
+		goto err_free_wire_ctx;
+	*gc = ctx;
+	return 0;
+err_free_wire_ctx:
+	rpc_free(ctx->gc_wire_ctx.data);
+err_free_ctx:
+	rpc_free(ctx);
+err:
+	*gc = NULL;
+	return err;
+}
+
+
+struct gss_upcall_msg {
+	struct rpc_pipe_msg msg;
+	struct list_head list;
+	struct rpc_wait_queue waitq;
+	uid_t	uid;
+	atomic_t count;
+};
+
+static void
+gss_release_msg(struct gss_upcall_msg *gss_msg)
+{
+	if (!atomic_dec_and_test(&gss_msg->count))
+		kfree(gss_msg);
+}
+
+static struct gss_upcall_msg *
+gss_find_upcall(struct gss_auth *gss_auth, uid_t uid)
+{
+	struct gss_upcall_msg *pos;
+	list_for_each_entry(pos, &gss_auth->upcalls, list) {
+		if (pos->uid == uid)
+			return pos;
+	}
+	return NULL;
+}
+
+static void
+gss_release_callback(struct rpc_task *task)
+{
+	struct rpc_clnt *clnt = task->tk_client;
+	struct gss_auth *gss_auth = container_of(clnt->cl_auth,
+			struct gss_auth, rpc_auth);
+	struct gss_upcall_msg *gss_msg;
+	spin_lock(&gss_auth->lock);
+	gss_msg = gss_find_upcall(gss_auth, task->tk_msg.rpc_cred->cr_uid);
+	if (gss_msg) {
+		rpc_wake_up(&gss_msg->waitq);
+		list_del(&gss_msg->list);
+		gss_release_msg(gss_msg);
+	}
+	spin_unlock(&gss_auth->lock);
+}
+
+static int
+gss_upcall(struct rpc_clnt *clnt, struct rpc_task *task, uid_t uid)
+{
+	struct gss_auth *gss_auth = container_of(clnt->cl_auth,
+			struct gss_auth, rpc_auth);
+	struct gss_upcall_msg *gss_msg, *gss_new = NULL;
+	struct rpc_pipe_msg *msg;
+	struct dentry *dentry = gss_auth->dentry;
+
+retry:
+	gss_msg = gss_find_upcall(gss_auth, uid);
+	if (!gss_msg) {
+		spin_unlock(&gss_auth->lock);
+		gss_new = kmalloc(sizeof(*gss_new), GFP_KERNEL);
+		spin_lock(&gss_auth->lock);
+		if (gss_new)
+			goto retry;
+		return -ENOMEM;
+	}
+	if (gss_msg)
+		goto out_sleep;
+	gss_msg = gss_new;
+	memset(gss_new, 0, sizeof(*gss_new));
+	INIT_LIST_HEAD(&gss_new->list);
+	INIT_RPC_WAITQ(&gss_new->waitq, "RPCSEC_GSS upcall waitq");
+	atomic_set(&gss_new->count, 1);
+	msg = &gss_new->msg;
+	msg->data = &gss_new->uid;
+	msg->len = sizeof(gss_new->uid);
+	gss_new->uid = uid;
+	list_add(&gss_new->list, &gss_auth->upcalls);
+	gss_new = NULL;
+	rpc_queue_upcall(dentry->d_inode, msg);
+out_sleep:
+	atomic_inc(&gss_msg->count);
+	rpc_sleep_on(&gss_msg->waitq, task, gss_release_callback, NULL);
+	if (gss_new)
+		kfree(gss_new);
+	return 0;
+}
+
+static ssize_t
+gss_pipe_upcall(struct file *filp, struct rpc_pipe_msg *msg,
+		char *dst, size_t buflen)
+{
+	char *data = (char *)msg->data + msg->copied;
+	ssize_t mlen = msg->len - msg->copied;
+	ssize_t left;
+
+	if (mlen > buflen)
+		mlen = buflen;
+	left = copy_to_user(dst, data, mlen);
+	return mlen - left;
+}
+
+static ssize_t
+gss_pipe_downcall(struct file *filp, const char *src, size_t mlen)
+{
+	char buf[1024];
+	struct xdr_netobj obj = {
+		.len	= mlen,
+		.data	= buf,
+	};
+	struct inode *inode = filp->f_dentry->d_inode;
+	struct rpc_inode *rpci = RPC_I(inode);
+	struct rpc_clnt *clnt;
+	struct rpc_auth *auth;
+	struct gss_auth *gss_auth;
+	struct gss_api_mech *mech;
+	struct auth_cred acred = { 0 };
+	struct rpc_cred *cred;
+	struct gss_upcall_msg *gss_msg;
+	struct gss_ctx *ctx;
+	ssize_t left;
+	int err;
+
+	if (mlen > sizeof(buf))
+		return -ENOSPC;
+	left = copy_from_user(buf, src, mlen);
+	if (left)
+		return -EFAULT;
+	down(&inode->i_sem);
+	clnt = rpci->rpc_client;
+	atomic_inc(&clnt->cl_users);
+	up(&inode->i_sem);
+	auth = clnt->cl_auth;
+	gss_auth = container_of(auth, struct gss_auth, rpc_auth);
+	mech = gss_auth->mech;
+	err = gss_parse_init_downcall(mech, &obj, &ctx, &acred.uid);
+	if (err)
+		goto err;
+	cred = rpcauth_lookup_credcache(auth, &acred, 0);
+	if (!cred)
+		goto err_release_ctx;
+	gss_cred_set_ctx(cred, ctx);
+	spin_lock(&gss_auth->lock);
+	gss_msg = gss_find_upcall(gss_auth, acred.uid);
+	if (gss_msg) {
+		rpc_wake_up(&gss_msg->waitq);
+		gss_release_msg(gss_msg);
+	}
+	spin_unlock(&gss_auth->lock);
+	return mlen;
+err_release_ctx:
+	gss_destroy_ctx(ctx);
+err:
+	rpc_release_client(clnt);
+	return err;
+}
+
+void
+gss_pipe_destroy_msg(struct rpc_pipe_msg *msg)
+{
+	struct gss_upcall_msg *gss_msg = container_of(msg, struct gss_upcall_msg, msg);
+
+	rpc_wake_up(&gss_msg->waitq);
+	gss_release_msg(gss_msg);
+}
 
 /* 
  * NOTE: we have the opportunity to use different 
@@ -120,12 +426,22 @@
 static struct rpc_auth *
 gss_create(struct rpc_clnt *clnt, rpc_authflavor_t flavor)
 {
+	struct gss_auth *gss_auth;
 	struct rpc_auth * auth;
 
 	dprintk("RPC: creating GSS authenticator for client %p\n",clnt);
 	MOD_INC_USE_COUNT;
-	if (!(auth = (struct rpc_auth *) rpc_allocate(0, sizeof(*auth))))
-		return NULL;
+	if (!(gss_auth = (struct gss_auth *) rpc_allocate(0, sizeof(*gss_auth))))
+		goto out_dec;
+	gss_auth->mech = gss_pseudoflavor_to_mech(flavor);
+	if (!gss_auth->mech) {
+		printk(KERN_WARNING "%s: Pseudoflavor %d not found!",
+				__FUNCTION__, flavor);
+		goto err_free;
+	}
+	INIT_LIST_HEAD(&gss_auth->upcalls);
+	spin_lock_init(&gss_auth->lock);
+	auth = &gss_auth->rpc_auth;
 	auth->au_cslack = GSS_CRED_SLACK >> 2;
 	auth->au_rslack = GSS_VERF_SLACK >> 2;
 	auth->au_expire = GSS_CRED_EXPIRE;
@@ -134,15 +450,31 @@
 
 	rpcauth_init_credcache(auth);
 
+	snprintf(gss_auth->path, sizeof(gss_auth->path), "%s/%s",
+			clnt->cl_pathname,
+			gss_auth->mech->gm_ops->name);
+	gss_auth->dentry = rpc_mkpipe(gss_auth->path, clnt, &gss_upcall_ops);
+	if (IS_ERR(gss_auth->dentry))
+		goto err_free;
+
 	return auth;
+err_free:
+	rpc_free(gss_auth);
+out_dec:
+	MOD_DEC_USE_COUNT;
+	return NULL;
 }
 
 static void
 gss_destroy(struct rpc_auth *auth)
 {
+	struct gss_auth *gss_auth;
 	dprintk("RPC: destroying GSS authenticator %p flavor %d\n",
 		auth, auth->au_flavor);
 
+	gss_auth = container_of(auth, struct gss_auth, rpc_auth);
+	rpc_unlink(gss_auth->path);
+
 	rpcauth_free_credcache(auth);
 
 	rpc_free(auth);
@@ -152,8 +484,9 @@
 /* gss_destroy_cred (and gss_destroy_ctx) are used to clean up after failure
  * to create a new cred or context, so they check that things have been
  * allocated before freeing them. */
-void
-gss_destroy_ctx(struct gss_ctx *ctx) {
+static void
+gss_destroy_ctx(struct gss_ctx *ctx)
+{
 
 	dprintk("RPC: gss_destroy_ctx\n");
 
@@ -177,7 +510,7 @@
 	dprintk("RPC: gss_destroy_cred \n");
 
 	if (cred->gc_ctx)
-		gss_destroy_ctx(cred->gc_ctx);
+		gss_put_ctx(cred->gc_ctx);
 	rpc_free(cred);
 }
 
@@ -224,8 +557,10 @@
 static u32 *
 gss_marshal(struct rpc_task *task, u32 *p, int ruid)
 {
-	struct gss_cred	*cred = (struct gss_cred *) task->tk_msg.rpc_cred;
-	struct gss_ctx	*ctx = cred->gc_ctx;
+	struct rpc_cred *cred = task->tk_msg.rpc_cred;
+	struct gss_cred	*gss_cred = container_of(cred, struct gss_cred,
+						 gc_base);
+	struct gss_ctx	*ctx = gss_cred_get_ctx(cred);
 	u32		*cred_len;
 	struct rpc_rqst *req = task->tk_rqstp;
 	struct rpc_clnt *clnt = task->tk_client;
@@ -246,11 +581,11 @@
 	*p++ = htonl(RPC_AUTH_GSS);
 	cred_len = p++;
 
-	service = gss_pseudoflavor_to_service(cred->gc_flavor);
+	service = gss_pseudoflavor_to_service(gss_cred->gc_flavor);
 	if (service == 0) {
 		dprintk("Bad pseudoflavor %d in gss_marshal\n",
-			cred->gc_flavor);
-		return NULL;
+			gss_cred->gc_flavor);
+		goto out_put_ctx;
 	}
 	spin_lock(&ctx->gc_seq_lock);
 	task->tk_gss_seqno = ctx->gc_seq++;
@@ -276,10 +611,13 @@
 	if(maj_stat != 0){
 		printk("gss_marshal: gss_get_mic FAILED (%d)\n",
 		       maj_stat);
-		return(NULL);
+		goto out_put_ctx;
 	}
 	p = xdr_encode_netobj(p, &bufout);
 	return p;
+out_put_ctx:
+	gss_put_ctx(ctx);
+	return NULL;
 }
 
 /*
@@ -288,9 +626,21 @@
 static int
 gss_refresh(struct rpc_task *task)
 {
-	/* Insert upcall here ! */
-	task->tk_msg.rpc_cred->cr_flags |= RPCAUTH_CRED_UPTODATE;
-	return task->tk_status = -EACCES;
+	struct rpc_clnt *clnt = task->tk_client;
+	struct gss_auth *gss_auth = container_of(clnt->cl_auth,
+			struct gss_auth, rpc_auth);
+	struct rpc_xprt *xprt = task->tk_xprt;
+	struct rpc_cred *cred = task->tk_msg.rpc_cred;
+	int err = 0;
+
+	task->tk_timeout = xprt->timeout.to_current;
+	spin_lock(&gss_auth->lock);
+	if (gss_cred_get_ctx(cred))
+		goto out;
+	err = gss_upcall(clnt, task, cred->cr_uid);
+out:
+	spin_unlock(&gss_auth->lock);
+	return err;
 }
 
 static u32 *
@@ -349,6 +699,12 @@
 	.crvalidate	= gss_validate,
 };
 
+static struct rpc_pipe_ops gss_upcall_ops = {
+	.upcall		= gss_pipe_upcall,
+	.downcall	= gss_pipe_downcall,
+	.destroy_msg	= gss_pipe_destroy_msg,
+};
+
 extern void gss_svc_ctx_init(void);
 
 /*


-------------------------------------------------------
This sf.net email is sponsored by: Influence the future 
of Java(TM) technology. Join the Java Community 
Process(SM) (JCP(SM)) program now. 
http://ads.sourceforge.net/cgi-bin/redirect.pl?sunm0004en
_______________________________________________
NFS maillist  -  NFS@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/nfs

Copyright © 2002, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds