NFS: Prevent memory allocation failure in nfsacl_encode()

nfsacl_encode() allocates memory in certain cases.  This of course
is not guaranteed to work.

Since commit 9f06c719 "SUNRPC: New xdr_streams XDR encoder API", the
kernel's XDR encoders can't return a result indicating possibly a
failure, so a memory allocation failure in nfsacl_encode() has become
fatal (ie, the XDR code Oopses) in some cases.

However, the allocated memory is a tiny fixed amount, on the order
of 40-50 bytes.  We can easily use a stack-allocated buffer for
this, with only a wee bit of nose-holding.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
This commit is contained in:
Chuck Lever 2011-01-21 03:05:38 +00:00 committed by Trond Myklebust
parent 731f3f482a
commit f61f6da0d5
4 changed files with 31 additions and 13 deletions

View File

@ -311,8 +311,8 @@ static int nfs3_proc_setacls(struct inode *inode, struct posix_acl *acl,
if (!nfs_server_capable(inode, NFS_CAP_ACLS)) if (!nfs_server_capable(inode, NFS_CAP_ACLS))
goto out; goto out;
/* We are doing this here, because XDR marshalling can only /* We are doing this here because XDR marshalling does not
return -ENOMEM. */ * return any results, it BUGs. */
status = -ENOSPC; status = -ENOSPC;
if (acl != NULL && acl->a_count > NFS_ACL_MAX_ENTRIES) if (acl != NULL && acl->a_count > NFS_ACL_MAX_ENTRIES)
goto out; goto out;

View File

@ -42,6 +42,11 @@ struct nfsacl_encode_desc {
gid_t gid; gid_t gid;
}; };
struct nfsacl_simple_acl {
struct posix_acl acl;
struct posix_acl_entry ace[4];
};
static int static int
xdr_nfsace_encode(struct xdr_array2_desc *desc, void *elem) xdr_nfsace_encode(struct xdr_array2_desc *desc, void *elem)
{ {
@ -99,17 +104,22 @@ int nfsacl_encode(struct xdr_buf *buf, unsigned int base, struct inode *inode,
.uid = inode->i_uid, .uid = inode->i_uid,
.gid = inode->i_gid, .gid = inode->i_gid,
}; };
struct nfsacl_simple_acl aclbuf;
int err; int err;
struct posix_acl *acl2 = NULL;
if (entries > NFS_ACL_MAX_ENTRIES || if (entries > NFS_ACL_MAX_ENTRIES ||
xdr_encode_word(buf, base, entries)) xdr_encode_word(buf, base, entries))
return -EINVAL; return -EINVAL;
if (encode_entries && acl && acl->a_count == 3) { if (encode_entries && acl && acl->a_count == 3) {
/* Fake up an ACL_MASK entry. */ struct posix_acl *acl2 = &aclbuf.acl;
acl2 = posix_acl_alloc(4, GFP_KERNEL);
if (!acl2) /* Avoid the use of posix_acl_alloc(). nfsacl_encode() is
return -ENOMEM; * invoked in contexts where a memory allocation failure is
* fatal. Fortunately this fake ACL is small enough to
* construct on the stack. */
memset(acl2, 0, sizeof(acl2));
posix_acl_init(acl2, 4);
/* Insert entries in canonical order: other orders seem /* Insert entries in canonical order: other orders seem
to confuse Solaris VxFS. */ to confuse Solaris VxFS. */
acl2->a_entries[0] = acl->a_entries[0]; /* ACL_USER_OBJ */ acl2->a_entries[0] = acl->a_entries[0]; /* ACL_USER_OBJ */
@ -120,8 +130,6 @@ int nfsacl_encode(struct xdr_buf *buf, unsigned int base, struct inode *inode,
nfsacl_desc.acl = acl2; nfsacl_desc.acl = acl2;
} }
err = xdr_encode_array2(buf, base + 4, &nfsacl_desc.desc); err = xdr_encode_array2(buf, base + 4, &nfsacl_desc.desc);
if (acl2)
posix_acl_release(acl2);
if (!err) if (!err)
err = 8 + nfsacl_desc.desc.elem_size * err = 8 + nfsacl_desc.desc.elem_size *
nfsacl_desc.desc.array_len; nfsacl_desc.desc.array_len;

View File

@ -22,6 +22,7 @@
#include <linux/errno.h> #include <linux/errno.h>
EXPORT_SYMBOL(posix_acl_init);
EXPORT_SYMBOL(posix_acl_alloc); EXPORT_SYMBOL(posix_acl_alloc);
EXPORT_SYMBOL(posix_acl_clone); EXPORT_SYMBOL(posix_acl_clone);
EXPORT_SYMBOL(posix_acl_valid); EXPORT_SYMBOL(posix_acl_valid);
@ -31,6 +32,16 @@ EXPORT_SYMBOL(posix_acl_create_masq);
EXPORT_SYMBOL(posix_acl_chmod_masq); EXPORT_SYMBOL(posix_acl_chmod_masq);
EXPORT_SYMBOL(posix_acl_permission); EXPORT_SYMBOL(posix_acl_permission);
/*
* Init a fresh posix_acl
*/
void
posix_acl_init(struct posix_acl *acl, int count)
{
atomic_set(&acl->a_refcount, 1);
acl->a_count = count;
}
/* /*
* Allocate a new ACL with the specified number of entries. * Allocate a new ACL with the specified number of entries.
*/ */
@ -40,10 +51,8 @@ posix_acl_alloc(int count, gfp_t flags)
const size_t size = sizeof(struct posix_acl) + const size_t size = sizeof(struct posix_acl) +
count * sizeof(struct posix_acl_entry); count * sizeof(struct posix_acl_entry);
struct posix_acl *acl = kmalloc(size, flags); struct posix_acl *acl = kmalloc(size, flags);
if (acl) { if (acl)
atomic_set(&acl->a_refcount, 1); posix_acl_init(acl, count);
acl->a_count = count;
}
return acl; return acl;
} }

View File

@ -71,6 +71,7 @@ posix_acl_release(struct posix_acl *acl)
/* posix_acl.c */ /* posix_acl.c */
extern void posix_acl_init(struct posix_acl *, int);
extern struct posix_acl *posix_acl_alloc(int, gfp_t); extern struct posix_acl *posix_acl_alloc(int, gfp_t);
extern struct posix_acl *posix_acl_clone(const struct posix_acl *, gfp_t); extern struct posix_acl *posix_acl_clone(const struct posix_acl *, gfp_t);
extern int posix_acl_valid(const struct posix_acl *); extern int posix_acl_valid(const struct posix_acl *);