linux/net/sunrpc/auth_unix.c
Trond Myklebust 8a3177604b SUNRPC: Fix a lock recursion in the auth_gss downcall
When we look up a new cred in the auth_gss downcall so that we can stuff
 the credcache, we do not want that lookup to queue up an upcall in order
 to initialise it. To do an upcall here not only redundant, but since we
 are already holding the inode->i_mutex, it will trigger a lock recursion.

 This patch allows rpcauth cache searches to indicate that they can cope
 with uninitialised credentials.

 Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
2006-02-01 12:52:23 -05:00

240 lines
5.4 KiB
C

/*
* linux/net/sunrpc/auth_unix.c
*
* UNIX-style authentication; no AUTH_SHORT support
*
* Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de>
*/
#include <linux/types.h>
#include <linux/sched.h>
#include <linux/module.h>
#include <linux/sunrpc/clnt.h>
#include <linux/sunrpc/auth.h>
#define NFS_NGROUPS 16
struct unx_cred {
struct rpc_cred uc_base;
gid_t uc_gid;
gid_t uc_gids[NFS_NGROUPS];
};
#define uc_uid uc_base.cr_uid
#define uc_count uc_base.cr_count
#define uc_flags uc_base.cr_flags
#define uc_expire uc_base.cr_expire
#define UNX_CRED_EXPIRE (60 * HZ)
#define UNX_WRITESLACK (21 + (UNX_MAXNODENAME >> 2))
#ifdef RPC_DEBUG
# define RPCDBG_FACILITY RPCDBG_AUTH
#endif
static struct rpc_auth unix_auth;
static struct rpc_cred_cache unix_cred_cache;
static struct rpc_credops unix_credops;
static struct rpc_auth *
unx_create(struct rpc_clnt *clnt, rpc_authflavor_t flavor)
{
dprintk("RPC: creating UNIX authenticator for client %p\n", clnt);
if (atomic_inc_return(&unix_auth.au_count) == 0)
unix_cred_cache.nextgc = jiffies + (unix_cred_cache.expire >> 1);
return &unix_auth;
}
static void
unx_destroy(struct rpc_auth *auth)
{
dprintk("RPC: destroying UNIX authenticator %p\n", auth);
rpcauth_free_credcache(auth);
}
/*
* Lookup AUTH_UNIX creds for current process
*/
static struct rpc_cred *
unx_lookup_cred(struct rpc_auth *auth, struct auth_cred *acred, int flags)
{
return rpcauth_lookup_credcache(auth, acred, flags);
}
static struct rpc_cred *
unx_create_cred(struct rpc_auth *auth, struct auth_cred *acred, int flags)
{
struct unx_cred *cred;
int i;
dprintk("RPC: allocating UNIX cred for uid %d gid %d\n",
acred->uid, acred->gid);
if (!(cred = kmalloc(sizeof(*cred), GFP_KERNEL)))
return ERR_PTR(-ENOMEM);
atomic_set(&cred->uc_count, 1);
cred->uc_flags = RPCAUTH_CRED_UPTODATE;
if (flags & RPCAUTH_LOOKUP_ROOTCREDS) {
cred->uc_uid = 0;
cred->uc_gid = 0;
cred->uc_gids[0] = NOGROUP;
} else {
int groups = acred->group_info->ngroups;
if (groups > NFS_NGROUPS)
groups = NFS_NGROUPS;
cred->uc_uid = acred->uid;
cred->uc_gid = acred->gid;
for (i = 0; i < groups; i++)
cred->uc_gids[i] = GROUP_AT(acred->group_info, i);
if (i < NFS_NGROUPS)
cred->uc_gids[i] = NOGROUP;
}
cred->uc_base.cr_ops = &unix_credops;
return (struct rpc_cred *) cred;
}
static void
unx_destroy_cred(struct rpc_cred *cred)
{
kfree(cred);
}
/*
* Match credentials against current process creds.
* The root_override argument takes care of cases where the caller may
* request root creds (e.g. for NFS swapping).
*/
static int
unx_match(struct auth_cred *acred, struct rpc_cred *rcred, int flags)
{
struct unx_cred *cred = (struct unx_cred *) rcred;
int i;
if (!(flags & RPCAUTH_LOOKUP_ROOTCREDS)) {
int groups;
if (cred->uc_uid != acred->uid
|| cred->uc_gid != acred->gid)
return 0;
groups = acred->group_info->ngroups;
if (groups > NFS_NGROUPS)
groups = NFS_NGROUPS;
for (i = 0; i < groups ; i++)
if (cred->uc_gids[i] != GROUP_AT(acred->group_info, i))
return 0;
return 1;
}
return (cred->uc_uid == 0
&& cred->uc_gid == 0
&& cred->uc_gids[0] == (gid_t) NOGROUP);
}
/*
* Marshal credentials.
* Maybe we should keep a cached credential for performance reasons.
*/
static u32 *
unx_marshal(struct rpc_task *task, u32 *p)
{
struct rpc_clnt *clnt = task->tk_client;
struct unx_cred *cred = (struct unx_cred *) task->tk_msg.rpc_cred;
u32 *base, *hold;
int i;
*p++ = htonl(RPC_AUTH_UNIX);
base = p++;
*p++ = htonl(jiffies/HZ);
/*
* Copy the UTS nodename captured when the client was created.
*/
p = xdr_encode_array(p, clnt->cl_nodename, clnt->cl_nodelen);
*p++ = htonl((u32) cred->uc_uid);
*p++ = htonl((u32) cred->uc_gid);
hold = p++;
for (i = 0; i < 16 && cred->uc_gids[i] != (gid_t) NOGROUP; i++)
*p++ = htonl((u32) cred->uc_gids[i]);
*hold = htonl(p - hold - 1); /* gid array length */
*base = htonl((p - base - 1) << 2); /* cred length */
*p++ = htonl(RPC_AUTH_NULL);
*p++ = htonl(0);
return p;
}
/*
* Refresh credentials. This is a no-op for AUTH_UNIX
*/
static int
unx_refresh(struct rpc_task *task)
{
task->tk_msg.rpc_cred->cr_flags |= RPCAUTH_CRED_UPTODATE;
return 0;
}
static u32 *
unx_validate(struct rpc_task *task, u32 *p)
{
rpc_authflavor_t flavor;
u32 size;
flavor = ntohl(*p++);
if (flavor != RPC_AUTH_NULL &&
flavor != RPC_AUTH_UNIX &&
flavor != RPC_AUTH_SHORT) {
printk("RPC: bad verf flavor: %u\n", flavor);
return NULL;
}
size = ntohl(*p++);
if (size > RPC_MAX_AUTH_SIZE) {
printk("RPC: giant verf size: %u\n", size);
return NULL;
}
task->tk_auth->au_rslack = (size >> 2) + 2;
p += (size >> 2);
return p;
}
struct rpc_authops authunix_ops = {
.owner = THIS_MODULE,
.au_flavor = RPC_AUTH_UNIX,
#ifdef RPC_DEBUG
.au_name = "UNIX",
#endif
.create = unx_create,
.destroy = unx_destroy,
.lookup_cred = unx_lookup_cred,
.crcreate = unx_create_cred,
};
static
struct rpc_cred_cache unix_cred_cache = {
.expire = UNX_CRED_EXPIRE,
};
static
struct rpc_auth unix_auth = {
.au_cslack = UNX_WRITESLACK,
.au_rslack = 2, /* assume AUTH_NULL verf */
.au_ops = &authunix_ops,
.au_count = ATOMIC_INIT(0),
.au_credcache = &unix_cred_cache,
};
static
struct rpc_credops unix_credops = {
.cr_name = "AUTH_UNIX",
.crdestroy = unx_destroy_cred,
.crmatch = unx_match,
.crmarshal = unx_marshal,
.crrefresh = unx_refresh,
.crvalidate = unx_validate,
};