diff --git a/fs/ext3/Makefile b/fs/ext3/Makefile index e77766a..216ca8a 100644 --- a/fs/ext3/Makefile +++ b/fs/ext3/Makefile @@ -7,6 +7,6 @@ obj-$(CONFIG_EXT3_FS) += ext3.o ext3-y := balloc.o bitmap.o dir.o file.o fsync.o ialloc.o inode.o \ ioctl.o namei.o super.o symlink.o hash.o resize.o ext3_jbd.o -ext3-$(CONFIG_EXT3_FS_XATTR) += xattr.o xattr_user.o xattr_trusted.o +ext3-$(CONFIG_EXT3_FS_XATTR) += xattr.o xattr_user.o xattr_trusted.o xattr_nfsd.o ext3-$(CONFIG_EXT3_FS_POSIX_ACL) += acl.o ext3-$(CONFIG_EXT3_FS_SECURITY) += xattr_security.o diff --git a/fs/ext3/xattr.c b/fs/ext3/xattr.c index 71fb8d6..e264320 100644 --- a/fs/ext3/xattr.c +++ b/fs/ext3/xattr.c @@ -114,6 +114,7 @@ static const struct xattr_handler *ext3_xattr_handler_map[] = { #ifdef CONFIG_EXT3_FS_SECURITY [EXT3_XATTR_INDEX_SECURITY] = &ext3_xattr_security_handler, #endif + [EXT3_XATTR_INDEX_NFSD] = &ext3_xattr_nfsd_handler, }; const struct xattr_handler *ext3_xattr_handlers[] = { @@ -126,6 +127,7 @@ const struct xattr_handler *ext3_xattr_handlers[] = { #ifdef CONFIG_EXT3_FS_SECURITY &ext3_xattr_security_handler, #endif + &ext3_xattr_nfsd_handler, NULL }; diff --git a/fs/ext3/xattr.h b/fs/ext3/xattr.h index 377fe72..92eac0c 100644 --- a/fs/ext3/xattr.h +++ b/fs/ext3/xattr.h @@ -21,6 +21,7 @@ #define EXT3_XATTR_INDEX_TRUSTED 4 #define EXT3_XATTR_INDEX_LUSTRE 5 #define EXT3_XATTR_INDEX_SECURITY 6 +#define EXT3_XATTR_INDEX_NFSD 7 struct ext3_xattr_header { __le32 h_magic; /* magic number for identification */ @@ -63,6 +64,7 @@ extern const struct xattr_handler ext3_xattr_trusted_handler; extern const struct xattr_handler ext3_xattr_acl_access_handler; extern const struct xattr_handler ext3_xattr_acl_default_handler; extern const struct xattr_handler ext3_xattr_security_handler; +extern const struct xattr_handler ext3_xattr_nfsd_handler; extern ssize_t ext3_listxattr(struct dentry *, char *, size_t); diff --git a/fs/ext3/xattr_nfsd.c b/fs/ext3/xattr_nfsd.c new file mode 100644 index 0000000..7e38c35 --- /dev/null +++ b/fs/ext3/xattr_nfsd.c @@ -0,0 +1,58 @@ +/* + * linux/fs/ext3/xattr_nfsd.c + * Handler for nfsd extended attributes. + * + * Copyright (C) 2003 by Andreas Gruenbacher, + * Copyright (C) 2010 Red Hat, Inc., James Morris + */ +#include +#include +#include +#include +#include +#include +#include "xattr.h" + +static size_t ext3_xattr_nfsd_list(struct dentry *dentry, char *list, + size_t list_size, const char *name, + size_t name_len, int type) +{ + const size_t prefix_len = XATTR_NFSD_PREFIX_LEN; + const size_t total_len = prefix_len + name_len + 1; + + if (!capable(CAP_SYS_ADMIN)) + return 0; + + if (list && total_len <= list_size) { + memcpy(list, XATTR_NFSD_PREFIX, prefix_len); + memcpy(list+prefix_len, name, name_len); + list[prefix_len + name_len] = '\0'; + } + return total_len; +} + +static int ext3_xattr_nfsd_get(struct dentry *dentry, const char *name, + void *buffer, size_t size, int type) +{ + if (strcmp(name, "") == 0) + return -EINVAL; + return ext3_xattr_get(dentry->d_inode, EXT3_XATTR_INDEX_NFSD, + name, buffer, size); +} + +static int ext3_xattr_nfsd_set(struct dentry *dentry, const char *name, + const void *value, size_t size, int flags, + int type) +{ + if (strcmp(name, "") == 0) + return -EINVAL; + return ext3_xattr_set(dentry->d_inode, EXT3_XATTR_INDEX_NFSD, name, + value, size, flags); +} + +const struct xattr_handler ext3_xattr_nfsd_handler = { + .prefix = XATTR_NFSD_PREFIX, + .list = ext3_xattr_nfsd_list, + .get = ext3_xattr_nfsd_get, + .set = ext3_xattr_nfsd_set, +}; diff --git a/fs/nfs/Kconfig b/fs/nfs/Kconfig index a43d07e..d9d2c53 100644 --- a/fs/nfs/Kconfig +++ b/fs/nfs/Kconfig @@ -38,9 +38,13 @@ config NFS_V3 If unsure, say Y. +config NFS_V3_XATTR_API + def_bool n + config NFS_V3_ACL bool "NFS client support for the NFSv3 ACL protocol extension" depends on NFS_V3 + select NFS_V3_XATTR_API help Some NFS servers support an auxiliary NFSv3 ACL protocol that Sun added to Solaris but never became an official part of the @@ -60,6 +64,28 @@ config NFS_V3_ACL If unsure, say N. +config NFS_V3_XATTR + bool "NFS client support for the NFSv3 XATTR protocol extension (EXPERIMENTAL)" + depends on NFS_V3 && EXPERIMENTAL + select NFS_V3_XATTR_API + help + This option selects client suport for the Linux NFSv3 extended + attribute protocol extension (XATTR). + + This is a side-protocol which extends general support for Linux + extended attributes over the network, and is based on the GPLd + IRIX implmentation (although not wire-compatible with it). + + Only the user.* namespace is currently supported. When connected + to a server which also supports XATTR, the full range of extended + attribute system calls: + + getxattr(2), listxattr(2), setxattr(2) and removexattr(2) + + should work as expected. + + If unsure, say N. + config NFS_V4 bool "NFS client support for NFS version 4 (EXPERIMENTAL)" depends on NFS_FS && EXPERIMENTAL diff --git a/fs/nfs/Makefile b/fs/nfs/Makefile index da7fda6..b289d7e 100644 --- a/fs/nfs/Makefile +++ b/fs/nfs/Makefile @@ -11,6 +11,8 @@ nfs-y := client.o dir.o file.o getroot.o inode.o super.o nfs2xdr.o \ nfs-$(CONFIG_ROOT_NFS) += nfsroot.o nfs-$(CONFIG_NFS_V3) += nfs3proc.o nfs3xdr.o nfs-$(CONFIG_NFS_V3_ACL) += nfs3acl.o +nfs-$(CONFIG_NFS_V3_XATTR_API) += nfs3xattr.o +nfs-$(CONFIG_NFS_V3_XATTR) += nfs3xattr_handlers.o nfs-$(CONFIG_NFS_V4) += nfs4proc.o nfs4xdr.o nfs4state.o nfs4renewd.o \ delegation.o idmap.o \ callback.o callback_xdr.o callback_proc.o \ diff --git a/fs/nfs/client.c b/fs/nfs/client.c index 7ec9b34..b5adf31 100644 --- a/fs/nfs/client.c +++ b/fs/nfs/client.c @@ -98,6 +98,21 @@ struct rpc_program nfsacl_program = { }; #endif /* CONFIG_NFS_V3_ACL */ +#ifdef CONFIG_NFS_V3_XATTR +static struct rpc_stat nfs_xattr_rpcstat = { &nfs_xattr_program }; +static struct rpc_version * nfs_xattr_version[] = { + [3] = &nfs_xattr_version3, +}; + +struct rpc_program nfs_xattr_program = { + .name = "nfsxattr", + .number = NFS_XATTR_PROGRAM, + .nrvers = ARRAY_SIZE(nfs_xattr_version), + .version = nfs_xattr_version, + .stats = &nfs_xattr_rpcstat, +}; +#endif /* CONFIG_NFS_V3_XATTR */ + struct nfs_client_initdata { const char *hostname; const struct sockaddr *addr; @@ -707,6 +722,36 @@ static inline void nfs_init_server_aclclient(struct nfs_server *server) #endif /* + * Initialise an NFSv3 XATTR client connection + */ +#ifdef CONFIG_NFS_V3_XATTR +static void nfs_init_server_xattrclient(struct nfs_server *server) +{ + if (server->nfs_client->rpc_ops->version != 3) + goto out_no_xattr; + if (server->flags & NFS_MOUNT_NOXATTR) + goto out_no_xattr; + + server->client_xattr = rpc_bind_new_program(server->client, &nfs_xattr_program, 3); + if (IS_ERR(server->client_xattr)) + goto out_no_xattr; + + /* No errors! Assume that XATTR is supported */ + server->caps |= NFS_CAP_XATTR; + return; + +out_no_xattr: + server->caps &= ~NFS_CAP_XATTR; +} +#else +static inline void nfs_init_server_xattrclient(struct nfs_server *server) +{ + server->flags &= ~NFS_MOUNT_NOXATTR; + server->caps &= ~NFS_CAP_XATTR; +} +#endif + +/* * Create a general RPC client */ static int nfs_init_server_rpcclient(struct nfs_server *server, @@ -852,8 +897,12 @@ static int nfs_init_server(struct nfs_server *server, server->mountd_protocol = data->mount_server.protocol; server->namelen = data->namlen; - /* Create a client RPC handle for the NFSv3 ACL management interface */ + /* + * Create client RPC handles for the NFSv3 ACL and XATTR management + * interfaces + */ nfs_init_server_aclclient(server); + nfs_init_server_xattrclient(server); dprintk("<-- nfs_init_server() = 0 [new %p]\n", clp); return 0; diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index 782b431..5fce66f 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -94,10 +94,10 @@ const struct inode_operations nfs3_dir_inode_operations = { .permission = nfs_permission, .getattr = nfs_getattr, .setattr = nfs_setattr, - .listxattr = nfs3_listxattr, - .getxattr = nfs3_getxattr, - .setxattr = nfs3_setxattr, - .removexattr = nfs3_removexattr, + .listxattr = generic_listxattr, + .getxattr = generic_getxattr, + .setxattr = generic_setxattr, + .removexattr = generic_removexattr, }; #endif /* CONFIG_NFS_V3 */ diff --git a/fs/nfs/file.c b/fs/nfs/file.c index 36a5e74..6f6349f 100644 --- a/fs/nfs/file.c +++ b/fs/nfs/file.c @@ -91,10 +91,10 @@ const struct inode_operations nfs3_file_inode_operations = { .permission = nfs_permission, .getattr = nfs_getattr, .setattr = nfs_setattr, - .listxattr = nfs3_listxattr, - .getxattr = nfs3_getxattr, - .setxattr = nfs3_setxattr, - .removexattr = nfs3_removexattr, + .listxattr = generic_listxattr, + .getxattr = generic_getxattr, + .setxattr = generic_setxattr, + .removexattr = generic_removexattr, }; #endif /* CONFIG_NFS_v3 */ diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h index d8bd619..6aff3a9 100644 --- a/fs/nfs/internal.h +++ b/fs/nfs/internal.h @@ -4,6 +4,7 @@ #include "nfs4_fs.h" #include +#include #include #define NFS_MS_MASK (MS_RDONLY|MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_SYNCHRONOUS) @@ -282,6 +283,27 @@ static inline char *nfs_devname(const struct vfsmount *mnt_parent, dentry, buffer, buflen); } +/* nfsxattr.c */ +extern const struct xattr_handler *nfs3_xattr_handlers[]; + +extern int nfs3_proc_getxattr(struct inode *inode, const char *namespace, + const char *name, void *value, size_t size); +extern int nfs3_proc_setxattr(struct inode *inode, const char *namespace, + const char *name, const void *value, + size_t size, int flags); +extern int nfs3_proc_listxattr(struct inode *inode, char *list, + size_t list_len); +extern int nfs3_init_xattr(struct inode *dir, struct dentry *dentry); + +/* nfs3xattr_handers.c */ +extern struct xattr_handler nfs3_xattr_user_handler; +extern struct xattr_handler nfs3_xattr_trusted_handler; +extern struct xattr_handler nfs3_xattr_security_handler; + +/* nfs3acl.c */ +extern struct xattr_handler nfs3_xattr_acl_access_handler; +extern struct xattr_handler nfs3_xattr_acl_default_handler; + /* * Determine the actual block size (and log2 thereof) */ diff --git a/fs/nfs/nfs2xdr.c b/fs/nfs/nfs2xdr.c index 81cf142..f72880b 100644 --- a/fs/nfs/nfs2xdr.c +++ b/fs/nfs/nfs2xdr.c @@ -701,6 +701,9 @@ static struct { { NFSERR_SERVERFAULT, -EREMOTEIO }, { NFSERR_BADTYPE, -EBADTYPE }, { NFSERR_JUKEBOX, -EJUKEBOX }, +#ifdef CONFIG_NFS_V3_XATTR + { NFSERR_NODATA, -ENODATA }, +#endif { -1, -EIO } }; diff --git a/fs/nfs/nfs3acl.c b/fs/nfs/nfs3acl.c index 9f88c5f..8aa0929 100644 --- a/fs/nfs/nfs3acl.c +++ b/fs/nfs/nfs3acl.c @@ -10,64 +10,41 @@ #define NFSDBG_FACILITY NFSDBG_PROC -ssize_t nfs3_listxattr(struct dentry *dentry, char *buffer, size_t size) +static size_t nfs3_acl_xattr_list(struct dentry *dentry, + char *list, size_t list_len, + const char *name, size_t name_len, + int acl_type) { - struct inode *inode = dentry->d_inode; struct posix_acl *acl; - int pos=0, len=0; + char *acl_name = (acl_type == ACL_TYPE_ACCESS) ? + POSIX_ACL_XATTR_ACCESS : POSIX_ACL_XATTR_DEFAULT; + size_t size = strlen(acl_name) + 1; -# define output(s) do { \ - if (pos + sizeof(s) <= size) { \ - memcpy(buffer + pos, s, sizeof(s)); \ - pos += sizeof(s); \ - } \ - len += sizeof(s); \ - } while(0) + acl = nfs3_proc_getacl(dentry->d_inode, acl_type); + if (!acl) + return 0; - acl = nfs3_proc_getacl(inode, ACL_TYPE_ACCESS); if (IS_ERR(acl)) return PTR_ERR(acl); - if (acl) { - output("system.posix_acl_access"); - posix_acl_release(acl); - } - if (S_ISDIR(inode->i_mode)) { - acl = nfs3_proc_getacl(inode, ACL_TYPE_DEFAULT); - if (IS_ERR(acl)) - return PTR_ERR(acl); - if (acl) { - output("system.posix_acl_default"); - posix_acl_release(acl); - } - } - -# undef output + if (list && size <= list_len) + memcpy(list, acl_name, size); - if (!buffer || len <= size) - return len; - return -ERANGE; + posix_acl_release(acl); + return size; } -ssize_t nfs3_getxattr(struct dentry *dentry, const char *name, - void *buffer, size_t size) +static int nfs3_acl_xattr_get(struct dentry *dentry, const char *name, + void *buffer, size_t size, int acl_type) { - struct inode *inode = dentry->d_inode; struct posix_acl *acl; - int type, error = 0; - - if (strcmp(name, POSIX_ACL_XATTR_ACCESS) == 0) - type = ACL_TYPE_ACCESS; - else if (strcmp(name, POSIX_ACL_XATTR_DEFAULT) == 0) - type = ACL_TYPE_DEFAULT; - else - return -EOPNOTSUPP; + int error = 0; - acl = nfs3_proc_getacl(inode, type); + acl = nfs3_proc_getacl(dentry->d_inode, acl_type); if (IS_ERR(acl)) return PTR_ERR(acl); else if (acl) { - if (type == ACL_TYPE_ACCESS && acl->a_count == 0) + if (acl_type == ACL_TYPE_ACCESS && acl->a_count == 0) error = -ENODATA; else error = posix_acl_to_xattr(acl, buffer, size); @@ -78,43 +55,42 @@ ssize_t nfs3_getxattr(struct dentry *dentry, const char *name, return error; } -int nfs3_setxattr(struct dentry *dentry, const char *name, - const void *value, size_t size, int flags) +static int nfs3_acl_xattr_set(struct dentry *dentry, const char *name, + const void *value, size_t size, int flags, + int acl_type) { - struct inode *inode = dentry->d_inode; struct posix_acl *acl; - int type, error; + int error; - if (strcmp(name, POSIX_ACL_XATTR_ACCESS) == 0) - type = ACL_TYPE_ACCESS; - else if (strcmp(name, POSIX_ACL_XATTR_DEFAULT) == 0) - type = ACL_TYPE_DEFAULT; - else - return -EOPNOTSUPP; + if (value == NULL && (flags & XATTR_REPLACE)) + acl = NULL; /* remove xattr */ + else { + acl = posix_acl_from_xattr(value, size); + if (IS_ERR(acl)) + return PTR_ERR(acl); + } - acl = posix_acl_from_xattr(value, size); - if (IS_ERR(acl)) - return PTR_ERR(acl); - error = nfs3_proc_setacl(inode, type, acl); + error = nfs3_proc_setacl(dentry->d_inode, acl_type, acl); posix_acl_release(acl); return error; } -int nfs3_removexattr(struct dentry *dentry, const char *name) -{ - struct inode *inode = dentry->d_inode; - int type; - - if (strcmp(name, POSIX_ACL_XATTR_ACCESS) == 0) - type = ACL_TYPE_ACCESS; - else if (strcmp(name, POSIX_ACL_XATTR_DEFAULT) == 0) - type = ACL_TYPE_DEFAULT; - else - return -EOPNOTSUPP; - - return nfs3_proc_setacl(inode, type, NULL); -} +struct xattr_handler nfs3_xattr_acl_access_handler = { + .prefix = POSIX_ACL_XATTR_ACCESS, + .flags = ACL_TYPE_ACCESS, + .list = nfs3_acl_xattr_list, + .get = nfs3_acl_xattr_get, + .set = nfs3_acl_xattr_set, +}; + +struct xattr_handler nfs3_xattr_acl_default_handler = { + .prefix = POSIX_ACL_XATTR_DEFAULT, + .flags = ACL_TYPE_DEFAULT, + .list = nfs3_acl_xattr_list, + .get = nfs3_acl_xattr_get, + .set = nfs3_acl_xattr_set, +}; static void __nfs3_forget_cached_acls(struct nfs_inode *nfsi) { diff --git a/fs/nfs/nfs3proc.c b/fs/nfs/nfs3proc.c index fabb4f2..6d71153 100644 --- a/fs/nfs/nfs3proc.c +++ b/fs/nfs/nfs3proc.c @@ -386,6 +386,9 @@ nfs3_proc_create(struct inode *dir, struct dentry *dentry, struct iattr *sattr, goto out; } status = nfs3_proc_set_default_acl(dir, dentry->d_inode, mode); + if (status != 0) + goto out; + status = nfs3_init_xattr(dir, dentry); out: nfs3_free_createdata(data); dprintk("NFS reply create: %d\n", status); @@ -565,6 +568,9 @@ nfs3_proc_mkdir(struct inode *dir, struct dentry *dentry, struct iattr *sattr) goto out; status = nfs3_proc_set_default_acl(dir, dentry->d_inode, mode); + if (status != 0) + goto out; + status = nfs3_init_xattr(dir, dentry); out: nfs3_free_createdata(data); dprintk("NFS reply mkdir: %d\n", status); @@ -702,6 +708,9 @@ nfs3_proc_mknod(struct inode *dir, struct dentry *dentry, struct iattr *sattr, if (status != 0) goto out; status = nfs3_proc_set_default_acl(dir, dentry->d_inode, mode); + if (status != 0) + goto out; + status = nfs3_init_xattr(dir, dentry); out: nfs3_free_createdata(data); dprintk("NFS reply mknod: %d\n", status); diff --git a/fs/nfs/nfs3xattr.c b/fs/nfs/nfs3xattr.c new file mode 100644 index 0000000..3295b17 --- /dev/null +++ b/fs/nfs/nfs3xattr.c @@ -0,0 +1,290 @@ +/* + * Extended attribute (xattr) API and protocol for NFSv3. + * + * Based on the ACL code. + * + * Copyright (C) 2009 Red Hat, Inc., James Morris + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + */ +#include +#include +#include +#include + +#include "internal.h" + +#define NFSDBG_FACILITY NFSDBG_PROC + +const struct xattr_handler *nfs3_xattr_handlers[] = { +#ifdef CONFIG_NFS_V3_XATTR + &nfs3_xattr_user_handler, + &nfs3_xattr_trusted_handler, + &nfs3_xattr_security_handler, +#endif +#ifdef CONFIG_NFS_V3_ACL + &nfs3_xattr_acl_access_handler, + &nfs3_xattr_acl_default_handler, +#endif + NULL +}; + +#ifdef CONFIG_NFS_V3_XATTR +/* + * XATTR protocol + */ + +/* + * Call GETXATTR + * + * FIXME: + * - Cache xattrs + * - Handle size probing + */ +int nfs3_proc_getxattr(struct inode *inode, const char *namespace, + const char *name, void *value, size_t size) +{ + int status; + struct nfs_fattr fattr; + struct nfs_server *server = NFS_SERVER(inode); + struct nfs3_getxattrargs args = { + .fh = NFS_FH(inode), + }; + struct nfs3_getxattrres res = { + .fattr = &fattr, + }; + struct rpc_message msg = { + .rpc_argp = &args, + .rpc_resp = &res, + }; + + if (!name || !*name) + return -EINVAL; + + if (size > XATTR_SIZE_MAX) + return -EINVAL; + + if (!nfs_server_capable(inode, NFS_CAP_XATTR)) + return -EOPNOTSUPP; + + status = nfs_revalidate_inode(server, inode); + if (status < 0) + return status; + + /* + * Applications usually first probe the xattr value size, then + * perform a full call. For now, just return a dummy value. + */ + if (!size || !value) + return 4096; + + args.xattr_namespace = namespace; + args.xattr_name = name; + args.xattr_size_max = size; + + /* + * FIXME + * + * This is ugly. We pre-allocate a buffer for the XDR layer to use, + * passing the size of the buffer via xattr_val_len, which is + * updated with the actual length decoded. We should investigate + * using the page-based interface used by ACLs and others, or some + * other better way. + */ + res.xattr_val_len = size; + res.xattr_val = kmalloc(size, GFP_KERNEL); + if (!res.xattr_val) + return -ENOMEM; + + dprintk("NFS call getxattr %s%s %zd\n", namespace, name, size); + + msg.rpc_proc = &server->client_xattr->cl_procinfo[XATTRPROC3_GETXATTR]; + nfs_fattr_init(&fattr); + status = rpc_call_sync(server->client_xattr, &msg, 0); + + dprintk("NFS reply getxattr: status=%d len=%d\n", + status, res.xattr_val_len); + + switch (status) { + case 0: + status = nfs_refresh_inode(inode, &fattr); + break; + case -EPFNOSUPPORT: + case -EPROTONOSUPPORT: + dprintk("NFS_V3_XATTR extension not supported; disabling\n"); + server->caps &= ~NFS_CAP_XATTR; + case -ENOTSUPP: + status = -EOPNOTSUPP; + default: + goto cleanup; + } + + status = res.xattr_val_len; + if (status <= size) + memcpy(value, res.xattr_val, status); + +cleanup: + kfree(res.xattr_val); + return status; +} + +/* + * Call SETXATTR or RMXATTR + * + * RMXATTR is invoked with a NULL buffer and XATTR_REPLACE. + * + */ +int nfs3_proc_setxattr(struct inode *inode, const char *namespace, + const char *name, const void *value, + size_t size, int flags) + +{ + int status; + struct nfs_fattr fattr; + struct nfs_server *server = NFS_SERVER(inode); + struct nfs3_setxattrargs args = { + .fh = NFS_FH(inode), + }; + struct nfs3_setxattrres res = { + .fattr = &fattr, + }; + struct rpc_message msg = { + .rpc_argp = &args, + .rpc_resp = &res, + }; + + if (!name || !*name) + return -EINVAL; + + if (!nfs_server_capable(inode, NFS_CAP_XATTR)) + return -EOPNOTSUPP; + + status = nfs_revalidate_inode(server, inode); + if (status < 0) + return status; + + args.xattr_namespace = namespace; + args.xattr_name = name; + args.xattr_flags = flags; + args.xattr_val = value; + args.xattr_val_len = size; + + dprintk("NFS call setxattr %s%s %zd 0x%08x\n", + namespace, name, size, flags); + + msg.rpc_proc = &server->client_xattr->cl_procinfo[XATTRPROC3_SETXATTR]; + nfs_fattr_init(&fattr); + status = rpc_call_sync(server->client_xattr, &msg, 0); + + dprintk("NFS reply setxattr: status=%d\n", status); + + switch (status) { + case 0: + status = nfs_refresh_inode(inode, &fattr); + break; + case -EPFNOSUPPORT: + case -EPROTONOSUPPORT: + dprintk("NFS_V3_XATTR extension not supported; disabling\n"); + server->caps &= ~NFS_CAP_XATTR; + case -ENOTSUPP: + status = -EOPNOTSUPP; + default: + break; + } + return status; +} + +/* + * Call LISTXATTR + */ +int nfs3_proc_listxattr(struct inode *inode, char *list, size_t list_len) +{ + int status; + struct nfs_fattr fattr; + struct nfs_server *server = NFS_SERVER(inode); + struct nfs3_listxattrargs args = { + .fh = NFS_FH(inode), + }; + struct nfs3_listxattrres res = { + .fattr = &fattr, + }; + struct rpc_message msg = { + .rpc_argp = &args, + .rpc_resp = &res, + }; + + if (list_len > XATTR_LIST_MAX) + return -EINVAL; + + if (!nfs_server_capable(inode, NFS_CAP_XATTR)) + return -EOPNOTSUPP; + + dprintk("NFS call listxattr %zd\n", list_len); + + /* FIXME: handle probes */ + if (!list || !list_len) + return 1024; + + args.xattr_list_max = list_len; + + /* FIXME (see comments for getxattr) */ + res.xattr_list_len = list_len; + res.xattr_list = kmalloc(list_len, GFP_KERNEL); + if (!res.xattr_list) + return -ENOMEM; + + msg.rpc_proc = &server->client_xattr->cl_procinfo[XATTRPROC3_LISTXATTR]; + nfs_fattr_init(&fattr); + status = rpc_call_sync(server->client_xattr, &msg, 0); + + dprintk("NFS reply listxattr: status=%d\n", status); + + switch (status) { + case 0: + status = nfs_refresh_inode(inode, &fattr); + break; + case -EPFNOSUPPORT: + case -EPROTONOSUPPORT: + dprintk("NFS_V3_XATTR extension not supported; disabling\n"); + server->caps &= ~NFS_CAP_XATTR; + case -ENOTSUPP: + status = -EOPNOTSUPP; + default: + goto cleanup; + } + + status = res.xattr_list_len; + if (status <= list_len) + memcpy(list, res.xattr_list, status); + +cleanup: + kfree(res.xattr_list); + return status; +} + +/* + * Create an xattr for a newly created file, if required by the security + * subsystem. + */ +int nfs3_init_xattr(struct inode *dir, struct dentry *dentry) +{ + int ret; + size_t len; + void *val; + struct inode *inode = dentry->d_inode; + + ret = security_inode_init_security(inode, dir, NULL, &val, &len); + if (ret) { + if (ret == -EOPNOTSUPP) + return 0; + return ret; + } + + ret = security_inode_setsecctx(dentry, val, len); + kfree(val); + + return ret; +} +#endif /* CONFIG_NFS_V3_XATTR */ diff --git a/fs/nfs/nfs3xattr_handlers.c b/fs/nfs/nfs3xattr_handlers.c new file mode 100644 index 0000000..f7eb561 --- /dev/null +++ b/fs/nfs/nfs3xattr_handlers.c @@ -0,0 +1,121 @@ +/* + * Client support for the NFS_XATTR protocol. + * + * Copyright (C) 2009 Red Hat, Inc., James Morris + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + */ +#include +#include +#include +#include + +#include "internal.h" + +#define NFSDBG_FACILITY NFSDBG_PROC + +/* + * 'user' namespace + */ + +/* + * Call the LISTXATTR procedure only once per syscall, when the user + * handler is invoked. + */ +static size_t nfs3_user_xattr_list(struct dentry *dentry, char *list, + size_t list_len, const char *name, + size_t name_len, int hflags) +{ + + return nfs3_proc_listxattr(dentry->d_inode, list, list_len); +} + +static size_t nfs3_noop_xattr_list(struct dentry *dentry, char *list, + size_t list_len, const char *name, + size_t name_len, int hflags) +{ + return 0; +} + +static int nfs3_user_xattr_get(struct dentry *dentry, const char *name, + void *buffer, size_t size, int hflags) +{ + return nfs3_proc_getxattr(dentry->d_inode, XATTR_USER_PREFIX, + name, buffer, size); +} + +static int nfs3_user_xattr_set(struct dentry *dentry, const char *name, + const void *value, size_t size, + int flags, int hflags) +{ + return nfs3_proc_setxattr(dentry->d_inode, XATTR_USER_PREFIX, + name, value, size, flags); +} + +/* + * 'trusted' namespace + */ +struct xattr_handler nfs3_xattr_user_handler = { + .prefix = XATTR_USER_PREFIX, + .list = nfs3_user_xattr_list, + .get = nfs3_user_xattr_get, + .set = nfs3_user_xattr_set, +}; + +static int nfs3_trusted_xattr_get(struct dentry *dentry, const char *name, + void *buffer, size_t size, int hflags) +{ + return nfs3_proc_getxattr(dentry->d_inode, XATTR_TRUSTED_PREFIX, + name, buffer, size); +} + +static int nfs3_trusted_xattr_set(struct dentry *dentry, const char *name, + const void *value, size_t size, + int flags, int hflags) +{ + return nfs3_proc_setxattr(dentry->d_inode, XATTR_TRUSTED_PREFIX, + name, value, size, flags); +} + +struct xattr_handler nfs3_xattr_trusted_handler = { + .prefix = XATTR_TRUSTED_PREFIX, + .list = nfs3_noop_xattr_list, + .get = nfs3_trusted_xattr_get, + .set = nfs3_trusted_xattr_set, +}; + +/* + * 'security' namespace + */ +static int nfs3_security_xattr_get(struct dentry *dentry, const char *name, + void *buffer, size_t size, int hflags) +{ + return nfs3_proc_getxattr(dentry->d_inode, XATTR_SECURITY_PREFIX, + name, buffer, size); +} + +static int nfs3_security_xattr_set(struct dentry *dentry, const char *name, + const void *value, size_t size, + int flags, int hflags) +{ + int ret; + + ret = nfs3_proc_setxattr(dentry->d_inode, XATTR_SECURITY_PREFIX, + name, value, size, flags); + + /* FIXME: once size probing is fixed, don't translate to zero */ + if ((flags & XATTR_REPLACE) && ret == -ENODATA) { + printk(KERN_DEBUG "%s: ignoring -ENODATA (fixme)\n", __func__); + ret = 0; + } + return ret; +} + +struct xattr_handler nfs3_xattr_security_handler = { + .prefix = XATTR_SECURITY_PREFIX, + .list = nfs3_noop_xattr_list, + .get = nfs3_security_xattr_get, + .set = nfs3_security_xattr_set, +}; diff --git a/fs/nfs/nfs3xdr.c b/fs/nfs/nfs3xdr.c index 75dcfc7..08d5ec9 100644 --- a/fs/nfs/nfs3xdr.c +++ b/fs/nfs/nfs3xdr.c @@ -87,6 +87,26 @@ #define ACL3_setaclres_sz (1+NFS3_post_op_attr_sz) /* + * FIXME: currently, the RPC layer will allocate the maximum buffer size + * here for each call (which can be ~ 64k). The Labeled NFS prototype code + * uses 4k, although we should not impose limits for NFS which don't exist + * in the OS unless absolutely necsssary. We likely need a dynamic scheme + * here, possibly using pages. + */ +#define XATTR3_xattrname_sz (1+(XATTR_NAME_MAX>>2)) +#define XATTR3_xattrval_sz (1+(XATTR_SIZE_MAX>>2)) +#define XATTR3_xattrlist_sz (1+(XATTR_LIST_MAX>>2)) + +#define XATTR3_getxattrargs_sz (NFS3_fh_sz+XATTR3_xattrname_sz+1) +#define XATTR3_getxattrres_sz (1+NFS3_post_op_attr_sz+XATTR3_xattrval_sz) + +#define XATTR3_setxattrargs_sz (NFS3_fh_sz+XATTR3_xattrname_sz+XATTR3_xattrval_sz+1) +#define XATTR3_setxattrres_sz (1+NFS3_post_op_attr_sz) + +#define XATTR3_listxattrargs_sz (NFS3_fh_sz+1) +#define XATTR3_listxattrres_sz (1+NFS3_post_op_attr_sz+XATTR3_xattrlist_sz) + +/* * Map file type to S_IFMT bits */ static const umode_t nfs_type2fmt[] = { @@ -726,6 +746,72 @@ nfs3_xdr_setaclargs(struct rpc_rqst *req, __be32 *p, } #endif /* CONFIG_NFS_V3_ACL */ +#ifdef CONFIG_NFS_V3_XATTR +/* + * Special case of xdr_encode_opaque, where the xattr helpers hand us + * separate namespace and name buffers, which we encode as a single XDR + * string over the wire. Neither namespace nor name may be empty or null. + */ +static __be32 *xattr_encode_name(__be32 *p, const char *namespace, const char *name) +{ + unsigned int nslen, namelen, totlen, quadlen, padding; + + nslen = strlen(namespace); + namelen = strlen(name); + totlen = nslen + namelen; + quadlen = XDR_QUADLEN(totlen); + padding = (quadlen << 2) - totlen; + + *p++ = cpu_to_be32(totlen); + memcpy(p, namespace, nslen); + memcpy((char *)p + nslen, name, namelen); + + if (padding != 0) + memset((char *)p + totlen, 0, padding); + p += quadlen; + return p; +} + +/* + * Encode GETXATTR arguments + */ +static int nfs3_xdr_getxattrargs(struct rpc_rqst *req, __be32 *p, + struct nfs3_getxattrargs *args) +{ + p = xdr_encode_fhandle(p, args->fh); + p = xattr_encode_name(p, args->xattr_namespace, args->xattr_name); + *p++ = htonl(args->xattr_size_max); + req->rq_slen = xdr_adjust_iovec(req->rq_svec, p); + return 0; +} + +/* + * Encode SETXATTR arguments + */ +static int nfs3_xdr_setxattrargs(struct rpc_rqst *req, __be32 *p, + struct nfs3_setxattrargs *args) +{ + p = xdr_encode_fhandle(p, args->fh); + p = xattr_encode_name(p, args->xattr_namespace, args->xattr_name); + p = xdr_encode_array(p, args->xattr_val, args->xattr_val_len); + *p++ = htonl(args->xattr_flags); + req->rq_slen = xdr_adjust_iovec(req->rq_svec, p); + return 0; +} + +/* + * Encode LISTXATTR arguments + */ +static int nfs3_xdr_listxattrargs(struct rpc_rqst *req, __be32 *p, + struct nfs3_listxattrargs *args) +{ + p = xdr_encode_fhandle(p, args->fh); + *p++ = htonl(args->xattr_list_max); + req->rq_slen = xdr_adjust_iovec(req->rq_svec, p); + return 0; +} +#endif /* CONFIG_NFS_V3_XATTR */ + /* * NFS XDR decode functions */ @@ -1135,6 +1221,69 @@ nfs3_xdr_setaclres(struct rpc_rqst *req, __be32 *p, struct nfs_fattr *fattr) } #endif /* CONFIG_NFS_V3_ACL */ +#ifdef CONFIG_NFS_V3_XATTR +/* + * Decode GETXATTR reply + * + * FIXME: determine appropriate error returns + */ +static int nfs3_xdr_getxattrres(struct rpc_rqst *req, __be32 *p, + struct nfs3_getxattrres *res) +{ + char *xattr_val; + unsigned int xattr_max_size = res->xattr_val_len; + int status = ntohl(*p++); + + if (status != 0) + return nfs_stat_to_errno(status); + + p = xdr_decode_post_op_attr(p, res->fattr); + p = xdr_decode_string_inplace(p, &xattr_val, + &res->xattr_val_len, + xattr_max_size); + if (p == NULL) + return -EINVAL; + memcpy(res->xattr_val, xattr_val, res->xattr_val_len); + return 0; +} + +/* + * Decode SETXATTR reply + */ +static int nfs3_xdr_setxattrres(struct rpc_rqst *req, __be32 *p, + struct nfs3_setxattrres *res) +{ + int status = ntohl(*p++); + + if (status) + return nfs_stat_to_errno(status); + xdr_decode_post_op_attr(p, res->fattr); + return 0; +} + +/* + * Decode LISTXATTR reply + */ +static int nfs3_xdr_listxattrres(struct rpc_rqst *req, __be32 *p, + struct nfs3_listxattrres *res) +{ + char *xattr_list; + unsigned int size = res->xattr_list_len; + int status = ntohl(*p++); + + if (status != 0) + return nfs_stat_to_errno(status); + + p = xdr_decode_post_op_attr(p, res->fattr); + p = xdr_decode_string_inplace(p, &xattr_list, + &res->xattr_list_len, size); + if (p == NULL) + return -EINVAL; + memcpy(res->xattr_list, xattr_list, res->xattr_list_len); + return 0; +} +#endif /* CONFIG_NFS_V3_XATTR */ + #define PROC(proc, argtype, restype, timer) \ [NFS3PROC_##proc] = { \ .p_proc = NFS3PROC_##proc, \ @@ -1206,3 +1355,41 @@ struct rpc_version nfsacl_version3 = { .procs = nfs3_acl_procedures, }; #endif /* CONFIG_NFS_V3_ACL */ + +#ifdef CONFIG_NFS_V3_XATTR +static struct rpc_procinfo nfs3_xattr_procedures[] = { + [XATTRPROC3_GETXATTR] = { + .p_proc = XATTRPROC3_GETXATTR, + .p_encode = (kxdrproc_t) nfs3_xdr_getxattrargs, + .p_decode = (kxdrproc_t) nfs3_xdr_getxattrres, + .p_arglen = XATTR3_getxattrargs_sz, + .p_replen = XATTR3_getxattrres_sz, + .p_timer = 1, + .p_name = "GETXATTR", + }, + [XATTRPROC3_SETXATTR] = { + .p_proc = XATTRPROC3_SETXATTR, + .p_encode = (kxdrproc_t) nfs3_xdr_setxattrargs, + .p_decode = (kxdrproc_t) nfs3_xdr_setxattrres, + .p_arglen = XATTR3_setxattrargs_sz, + .p_replen = XATTR3_setxattrres_sz, + .p_timer = 1, + .p_name = "SETXATTR", + }, + [XATTRPROC3_LISTXATTR] = { + .p_proc = XATTRPROC3_LISTXATTR, + .p_encode = (kxdrproc_t) nfs3_xdr_listxattrargs, + .p_decode = (kxdrproc_t) nfs3_xdr_listxattrres, + .p_arglen = XATTR3_listxattrargs_sz, + .p_replen = XATTR3_listxattrres_sz, + .p_timer = 1, + .p_name = "LISTXATTR", + }, +}; + +struct rpc_version nfs_xattr_version3 = { + .number = 3, + .nrprocs = ARRAY_SIZE(nfs3_xattr_procedures), + .procs = nfs3_xattr_procedures, +}; +#endif /* CONFIG_NFS_V3_XATTR */ diff --git a/fs/nfs/nfsroot.c b/fs/nfs/nfsroot.c index 6bd19d8..cdc9644 100644 --- a/fs/nfs/nfsroot.c +++ b/fs/nfs/nfsroot.c @@ -128,6 +128,8 @@ enum { Opt_nointr, Opt_posix, Opt_noposix, Opt_cto, Opt_nocto, Opt_ac, Opt_noac, Opt_lock, Opt_nolock, Opt_v2, Opt_v3, Opt_udp, Opt_tcp, Opt_acl, Opt_noacl, + Opt_xattr, Opt_noxattr, + Opt_xattrsec, Opt_noxattrsec, /* Error token */ Opt_err }; @@ -164,6 +166,10 @@ static const match_table_t tokens __initconst = { {Opt_tcp, "tcp"}, {Opt_acl, "acl"}, {Opt_noacl, "noacl"}, + {Opt_xattr, "xattr"}, + {Opt_noxattr, "noxattr"}, + {Opt_xattrsec, "xattrsec"}, + {Opt_noxattrsec, "noxattrsec"}, {Opt_err, NULL} }; @@ -275,6 +281,12 @@ static int __init root_nfs_parse(char *name, char *buf) case Opt_noacl: nfs_data.flags |= NFS_MOUNT_NOACL; break; + case Opt_xattr: + nfs_data.flags &= ~NFS_MOUNT_NOXATTR; + break; + case Opt_noxattr: + nfs_data.flags |= NFS_MOUNT_NOXATTR; + break; default: printk(KERN_WARNING "Root-NFS: unknown " "option: %s\n", p); diff --git a/fs/nfs/super.c b/fs/nfs/super.c index 04214fc..f3def8c 100644 --- a/fs/nfs/super.c +++ b/fs/nfs/super.c @@ -77,6 +77,8 @@ enum { Opt_v2, Opt_v3, Opt_v4, Opt_udp, Opt_tcp, Opt_rdma, Opt_acl, Opt_noacl, + Opt_xattr, Opt_noxattr, + Opt_xattrsec, Opt_noxattrsec, Opt_rdirplus, Opt_nordirplus, Opt_sharecache, Opt_nosharecache, Opt_resvport, Opt_noresvport, @@ -134,6 +136,10 @@ static const match_table_t nfs_mount_option_tokens = { { Opt_rdma, "rdma" }, { Opt_acl, "acl" }, { Opt_noacl, "noacl" }, + { Opt_xattr, "xattr" }, + { Opt_noxattr, "noxattr" }, + { Opt_xattrsec, "xattrsec" }, + { Opt_noxattrsec, "noxattrsec" }, { Opt_rdirplus, "rdirplus" }, { Opt_nordirplus, "nordirplus" }, { Opt_sharecache, "sharecache" }, @@ -755,6 +761,7 @@ static void nfs_umount_begin(struct super_block *sb) server = NFS_SB(sb); /* -EIO all pending I/O */ + /* FIXME: client_xattr ? */ rpc = server->client_acl; if (!IS_ERR(rpc)) rpc_killall_tasks(rpc); @@ -1022,6 +1029,18 @@ static int nfs_parse_mount_options(char *raw, case Opt_noacl: mnt->flags |= NFS_MOUNT_NOACL; break; + case Opt_xattr: + mnt->flags &= ~NFS_MOUNT_NOXATTR; + break; + case Opt_noxattr: + mnt->flags |= NFS_MOUNT_NOXATTR; + break; + case Opt_xattrsec: + mnt->flags |= NFS_MOUNT_XATTRSEC; + break; + case Opt_noxattrsec: + mnt->flags &= ~NFS_MOUNT_XATTRSEC; + break; case Opt_rdirplus: mnt->flags &= ~NFS_MOUNT_NORDIRPLUS; break; @@ -2023,6 +2042,9 @@ static void nfs_fill_super(struct super_block *sb, */ sb->s_flags |= MS_POSIXACL; sb->s_time_gran = 1; +#ifdef CONFIG_NFS_V3_XATTR + sb->s_xattr = nfs3_xattr_handlers; +#endif } sb->s_op = &nfs_sops; diff --git a/fs/nfsd/Kconfig b/fs/nfsd/Kconfig index 503b9da..4252d16 100644 --- a/fs/nfsd/Kconfig +++ b/fs/nfsd/Kconfig @@ -64,6 +64,14 @@ config NFSD_V3_ACL If unsure, say N. +config NFSD_V3_XATTR + bool "NFS server support for the NFSv3 XATTR protocol extension (EXPERIMENTAL)" + depends on NFSD_V3 && EXPERIMENTAL + help + NFS server support for the NFSv3 XATTR protocol. + + If unsure, say N. + config NFSD_V4 bool "NFS server support for NFS version 4 (EXPERIMENTAL)" depends on NFSD && PROC_FS && EXPERIMENTAL diff --git a/fs/nfsd/Makefile b/fs/nfsd/Makefile index 9b118ee..e206b52 100644 --- a/fs/nfsd/Makefile +++ b/fs/nfsd/Makefile @@ -9,5 +9,6 @@ nfsd-y := nfssvc.o nfsctl.o nfsproc.o nfsfh.o vfs.o \ nfsd-$(CONFIG_NFSD_V2_ACL) += nfs2acl.o nfsd-$(CONFIG_NFSD_V3) += nfs3proc.o nfs3xdr.o nfsd-$(CONFIG_NFSD_V3_ACL) += nfs3acl.o +nfsd-$(CONFIG_NFSD_V3_XATTR) += nfs3xattr.o nfsd-$(CONFIG_NFSD_V4) += nfs4proc.o nfs4xdr.o nfs4state.o nfs4idmap.o \ nfs4acl.o nfs4callback.o nfs4recover.o diff --git a/fs/nfsd/nfs3xattr.c b/fs/nfsd/nfs3xattr.c new file mode 100644 index 0000000..d9a7785 --- /dev/null +++ b/fs/nfsd/nfs3xattr.c @@ -0,0 +1,383 @@ +/* + * Process version 3 NFSXATTR requests. + * + * Based on the NFSACL code by: + * Copyright (C) 2002-2003 Andreas Gruenbacher + * + * Copyright (C) 2009 Red Hat, Inc., James Morris + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + */ +#include +#include +#include +#include + +#include "nfsd.h" +#include "xdr3.h" +#include "vfs.h" +#include "cache.h" + +#define NFSDDBG_FACILITY NFSDDBG_PROC +#define RETURN_STATUS(st) { resp->status = (st); return (st); } + +/* NULL call */ +static __be32 nfsd3_proc_null(struct svc_rqst *rqstp, void *argp, void *resp) +{ + return nfs_ok; +} + +/* + * GETXATTR + * + * FIXME: + * - Implement shared xattr cache + * - Audit nfs error returns + */ +static __be32 nfsd3_proc_getxattr(struct svc_rqst * rqstp, + struct nfsd3_getxattrargs *argp, + struct nfsd3_getxattrres *resp) +{ + __be32 nfserr = nfserrno(-EINVAL); + svc_fh *fh; + void *value; + int ret; + char *name, *xattr_name = argp->xattr_name; + unsigned int size_max = argp->xattr_size_max; + unsigned int name_len = argp->xattr_name_len; + unsigned int name_tot_len = name_len + NFSD_XATTR_PREFIX_LEN; + + dprintk("nfsd: GETXATTR(3) %s %.*s %u\n", SVCFH_fmt(&argp->fh), + name_len, xattr_name, size_max); + + if (name_tot_len > XATTR_NAME_MAX) + RETURN_STATUS(nfserr); + + if (size_max > XATTR_SIZE_MAX) + RETURN_STATUS(nfserr); + + /* Probes must be handled by the client */ + if (size_max == 0) + RETURN_STATUS(nfserr); + + fh = fh_copy(&resp->fh, &argp->fh); + nfserr = fh_verify(rqstp, &resp->fh, 0, NFSD_MAY_READ); + if (nfserr) + RETURN_STATUS(nfserr); + + /* Convert xattr name to real string and add local prefix */ + name = kmalloc(name_tot_len + 1, GFP_KERNEL); + if (name == NULL) + RETURN_STATUS(nfserrno(-ENOMEM)); + + ret = snprintf(name, name_tot_len + 1, "%s%.*s", + NFSD_XATTR_PREFIX, name_len, xattr_name); + if (ret > name_tot_len) { + nfserr = nfserrno(-EINVAL); + goto cleanup; + } + + ret = nfsd_getxattr(fh->fh_dentry, name, &value); + if (ret <= 0) { + nfserr = nfserrno(ret); + goto cleanup; + } + + nfserr = 0; + resp->xattr_val = value; + resp->xattr_val_len = ret; + + /* FIXME: verify whether release func is called on error (cf. ACL code) */ + +cleanup: + kfree(name); + RETURN_STATUS(nfserr); +} + +/* cribbed from decode pathname */ +static __be32 *decode_xattrname(__be32 *p, char **namp, unsigned int *lenp) +{ + char *name; + unsigned int i; + + p = xdr_decode_string_inplace(p, namp, lenp, XATTR_NAME_MAX); + if (p != NULL) + for (i = 0, name = *namp; i < *lenp; i++, name++) + if (*name == '\0') + return NULL; + return p; +} + +static int nfs3svc_decode_getxattrargs(struct svc_rqst *rqstp, __be32 *p, + struct nfsd3_getxattrargs *argp) +{ + if (!(p = nfs3svc_decode_fh(p, &argp->fh))) + return 0; + if (!(p = decode_xattrname(p, &argp->xattr_name, &argp->xattr_name_len))) + return 0; + argp->xattr_size_max = ntohl(*p++); + return xdr_argsize_check(rqstp, p); +} + +static int nfs3svc_encode_getxattrres(struct svc_rqst *rqstp, __be32 *p, + struct nfsd3_getxattrres *resp) +{ + p = nfs3svc_encode_post_op_attr(rqstp, p, &resp->fh); + if (resp->status == 0) + p = xdr_encode_array(p, resp->xattr_val, resp->xattr_val_len); + return xdr_ressize_check(rqstp, p); +} + +static int nfs3svc_release_getxattr(struct svc_rqst *rqstp, __be32 *p, + struct nfsd3_getxattrres *resp) +{ + fh_put(&resp->fh); + kfree(resp->xattr_val); + return 1; +} + +/* + * SETXATTR and RMXATTR + * + * RMXATTR is detected with zero buffer len and XATTR_REPLACE. + * + */ +static __be32 nfsd3_proc_setxattr(struct svc_rqst * rqstp, + struct nfsd3_setxattrargs *argp, + struct nfsd3_setxattrres *resp) +{ + __be32 nfserr = nfserrno(-EINVAL); + svc_fh *fh; + int ret; + char *name, *xattr_name = argp->xattr_name; + unsigned int name_len = argp->xattr_name_len; + unsigned int val_len = argp->xattr_val_len; + unsigned int flags = argp->xattr_flags; + unsigned int name_tot_len = name_len + NFSD_XATTR_PREFIX_LEN; + + dprintk("nfsd: SETXATTR(3) %s %.*s %u %#x\n", SVCFH_fmt(&argp->fh), + name_len, xattr_name, val_len, flags); + + if (name_tot_len > XATTR_NAME_MAX) + RETURN_STATUS(nfserr); + + if (val_len > XATTR_SIZE_MAX) + RETURN_STATUS(nfserr); + + if (flags & ~(XATTR_CREATE|XATTR_REPLACE)) + RETURN_STATUS(nfserr); + + fh = fh_copy(&resp->fh, &argp->fh); + nfserr = fh_verify(rqstp, &resp->fh, 0, NFSD_MAY_SATTR); + if (nfserr) + RETURN_STATUS(nfserr); + + /* Convert xattr name to real string and add prefix */ + name = kmalloc(name_tot_len + 1, GFP_KERNEL); + if (name == NULL) + RETURN_STATUS(nfserrno(-ENOMEM)); + + ret = snprintf(name, name_tot_len + 1, "%s%.*s", + NFSD_XATTR_PREFIX, name_len, xattr_name); + + if (ret > name_tot_len) { + nfserr = nfserrno(-EINVAL); + goto cleanup; + } + + if (!val_len) { + if (flags & ~XATTR_REPLACE) { + nfserr = nfserrno(-EINVAL); + goto cleanup; + } + ret = vfs_removexattr(fh->fh_dentry, name); + } else + ret = vfs_setxattr(fh->fh_dentry, name, + argp->xattr_val, val_len, flags); + + nfserr = nfserrno(ret); + +cleanup: + kfree(name); + RETURN_STATUS(nfserr); +} + +static int nfs3svc_decode_setxattrargs(struct svc_rqst *rqstp, __be32 *p, + struct nfsd3_setxattrargs *argp) +{ + if (!(p = nfs3svc_decode_fh(p, &argp->fh))) + return 0; + if (!(p = decode_xattrname(p, &argp->xattr_name, &argp->xattr_name_len))) + return 0; + if (!(p = xdr_decode_string_inplace(p, &argp->xattr_val, + &argp->xattr_val_len, XATTR_SIZE_MAX))) + return 0; + argp->xattr_flags = ntohl(*p++); + return xdr_argsize_check(rqstp, p); +} + +static int nfs3svc_encode_setxattrres(struct svc_rqst *rqstp, __be32 *p, + struct nfsd3_setxattrres *resp) +{ + p = nfs3svc_encode_post_op_attr(rqstp, p, &resp->fh); + return xdr_ressize_check(rqstp, p); +} + +static int nfs3svc_release_setxattr(struct svc_rqst *rqstp, __be32 *p, + struct nfsd3_setxattrres *resp) +{ + fh_put(&resp->fh); + return 1; +} + +/* + * Search the xattr list for those which match the local NFSD prefix. For + * each, strip the prefix and copy out the rest of the xattr. + */ +static int nfsd3_filter_xattrs(const char *in, char *out, int i_len) +{ + int i_idx = 0, o_idx = 0; + + while (i_idx < i_len) { + const char *curr = in + i_idx; + int c_len = strlen(curr); + + if (!strncmp(NFSD_XATTR_PREFIX, curr, NFSD_XATTR_PREFIX_LEN)) { + strcpy(out + o_idx, curr + NFSD_XATTR_PREFIX_LEN); + o_idx += c_len - NFSD_XATTR_PREFIX_LEN + 1; + } + + i_idx += c_len + 1; + } + + return o_idx; +} + +/* + * LISTXATTR + */ +static __be32 nfsd3_proc_listxattr(struct svc_rqst * rqstp, + struct nfsd3_listxattrargs *argp, + struct nfsd3_listxattrres *resp) +{ + __be32 nfserr = nfserrno(-EINVAL); + svc_fh *fh; + char *list, *f_list; + int ret, f_len; + unsigned int list_max = argp->xattr_list_max; + + dprintk("nfsd: LISTXATTR(3) %s %u\n", SVCFH_fmt(&argp->fh), list_max); + + if (list_max > XATTR_LIST_MAX) + RETURN_STATUS(nfserr); + + /* Probes must be handled by the client */ + if (list_max == 0) + RETURN_STATUS(nfserr); + + fh = fh_copy(&resp->fh, &argp->fh); + nfserr = fh_verify(rqstp, &resp->fh, 0, NFSD_MAY_READ); + if (nfserr) + RETURN_STATUS(nfserr); + + list = kmalloc(list_max, GFP_ATOMIC); + if (list == NULL) + RETURN_STATUS(nfserrno(-ENOMEM)); + + ret = vfs_listxattr(fh->fh_dentry, list, list_max); + if (ret <= 0) { + if (ret == 0) + ret = -ENODATA; + kfree(list); + RETURN_STATUS(nfserrno(ret)); + } + + /* + * Filter the xattr list: translate and return only those stored + * with the correct prefix. The list is a series of nul-terminated + * strings. + */ + f_list = kmalloc(ret, GFP_ATOMIC); + if (f_list == NULL) { + kfree(list); + RETURN_STATUS(nfserrno(-ENOMEM)); + } + + f_len = nfsd3_filter_xattrs(list, f_list, ret); + kfree(list); + + nfserr = 0; + resp->xattr_list = f_list; + resp->xattr_list_len = f_len; + + RETURN_STATUS(nfserr); +} + +static int nfs3svc_decode_listxattrargs(struct svc_rqst *rqstp, __be32 *p, + struct nfsd3_listxattrargs *argp) +{ + if (!(p = nfs3svc_decode_fh(p, &argp->fh))) + return 0; + argp->xattr_list_max = ntohl(*p++); + return xdr_argsize_check(rqstp, p); +} + +static int nfs3svc_encode_listxattrres(struct svc_rqst *rqstp, __be32 *p, + struct nfsd3_listxattrres *resp) +{ + p = nfs3svc_encode_post_op_attr(rqstp, p, &resp->fh); + if (resp->status == 0) + p = xdr_encode_array(p, resp->xattr_list, resp->xattr_list_len); + return xdr_ressize_check(rqstp, p); +} + +static int nfs3svc_release_listxattr(struct svc_rqst *rqstp, __be32 *p, + struct nfsd3_listxattrres *resp) +{ + fh_put(&resp->fh); + kfree(resp->xattr_list); + return 1; +} + +#define ST 1 /* status */ +#define AT 21 /* attributes */ +#define pAT (1+AT) /* post attributes - conditional */ + +#define nfs3svc_decode_voidargs NULL +#define nfs3svc_release_void NULL +#define nfsd3_voidres nfsd3_voidargs +struct nfsd3_voidargs { int dummy; }; + +#define PROC(name, argt, rest, relt, cache, respsize) \ + { (svc_procfunc) nfsd3_proc_##name, \ + (kxdrproc_t) nfs3svc_decode_##argt##args, \ + (kxdrproc_t) nfs3svc_encode_##rest##res, \ + (kxdrproc_t) nfs3svc_release_##relt, \ + sizeof(struct nfsd3_##argt##args), \ + sizeof(struct nfsd3_##rest##res), \ + 0, \ + cache, \ + respsize, \ + } + +#define G_RSZ (ST+pAT+1+(XATTR_SIZE_MAX>>2)) +#define S_RSZ (ST+pAT) +#define L_RSZ (ST+pAT+1+(XATTR_LIST_MAX>>2)) + +static struct svc_procedure nfsd_xattr_procedures3[] = { + PROC(null, void, void, void, RC_NOCACHE, ST), + PROC(getxattr, getxattr, getxattr, getxattr, RC_NOCACHE, G_RSZ), + PROC(setxattr, setxattr, setxattr, setxattr, RC_NOCACHE, S_RSZ), + PROC(listxattr, listxattr, listxattr, listxattr, RC_NOCACHE, L_RSZ), +}; + +struct svc_version nfsd_xattr_version3 = { + .vs_vers = 3, + .vs_nproc = 4, + .vs_proc = nfsd_xattr_procedures3, + .vs_dispatch = nfsd_dispatch, + .vs_xdrsize = NFS3_SVC_XDRSIZE, + .vs_hidden = 1, +}; diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index 508941c..64945b5 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -1421,6 +1421,8 @@ static int create_proc_exports_entry(void) } #endif +extern void __init nfsd_prog_init(void); + static int __init init_nfsd(void) { int retval; @@ -1429,6 +1431,7 @@ static int __init init_nfsd(void) retval = nfs4_state_init(); /* nfs4 locking state */ if (retval) return retval; + nfsd_prog_init(); nfsd_stat_init(); /* Statistics */ retval = nfsd_reply_cache_init(); if (retval) diff --git a/fs/nfsd/nfsd.h b/fs/nfsd/nfsd.h index 7237776..b100345 100644 --- a/fs/nfsd/nfsd.h +++ b/fs/nfsd/nfsd.h @@ -166,6 +166,7 @@ void nfsd_lockd_shutdown(void); #define nfserr_cb_path_down cpu_to_be32(NFSERR_CB_PATH_DOWN) #define nfserr_locked cpu_to_be32(NFSERR_LOCKED) #define nfserr_wrongsec cpu_to_be32(NFSERR_WRONGSEC) +#define nfserr_nodata cpu_to_be32(NFSERR_NODATA) #define nfserr_badiomode cpu_to_be32(NFS4ERR_BADIOMODE) #define nfserr_badlayout cpu_to_be32(NFS4ERR_BADLAYOUT) #define nfserr_bad_session_digest cpu_to_be32(NFS4ERR_BAD_SESSION_DIGEST) diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c index a047ad6..e364367 100644 --- a/fs/nfsd/nfsproc.c +++ b/fs/nfsd/nfsproc.c @@ -744,6 +744,9 @@ nfserrno (int errno) { nfserr_notsupp, -EOPNOTSUPP }, { nfserr_toosmall, -ETOOSMALL }, { nfserr_serverfault, -ESERVERFAULT }, +#ifdef CONFIG_NFSD_V3_XATTR + { nfserr_nodata, -ENODATA }, +#endif }; int i; diff --git a/fs/nfsd/nfssvc.c b/fs/nfsd/nfssvc.c index 06b2a26..29096ae 100644 --- a/fs/nfsd/nfssvc.c +++ b/fs/nfsd/nfssvc.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "nfsd.h" #include "cache.h" @@ -87,6 +88,27 @@ static struct svc_stat nfsd_acl_svcstats = { }; #endif /* defined(CONFIG_NFSD_V2_ACL) || defined(CONFIG_NFSD_V3_ACL) */ +#ifdef CONFIG_NFSD_V3_XATTR +static struct svc_stat nfsd_xattr_svcstats; +static struct svc_version * nfsd_xattr_version[] = { + [3] = &nfsd_xattr_version3, +}; + +#define NFSD_XATTR_MINVERS 3 +#define NFSD_XATTR_NRVERS ARRAY_SIZE(nfsd_xattr_version) +static struct svc_version *nfsd_xattr_versions[NFSD_XATTR_NRVERS]; + +static struct svc_program nfsd_xattr_program = { + .pg_prog = NFS_XATTR_PROGRAM, + .pg_nvers = NFSD_XATTR_NRVERS, + .pg_vers = nfsd_xattr_versions, + .pg_name = "nfsxattr", + .pg_class = "nfsd", /* share nfsd auth */ + .pg_stats = &nfsd_xattr_svcstats, + .pg_authenticate = &svc_set_client, +}; +#endif /* CONFIG_NFSD_V3_XATTR */ + static struct svc_version * nfsd_version[] = { [2] = &nfsd_version2, #if defined(CONFIG_NFSD_V3) @@ -102,9 +124,6 @@ static struct svc_version * nfsd_version[] = { static struct svc_version *nfsd_versions[NFSD_NRVERS]; struct svc_program nfsd_program = { -#if defined(CONFIG_NFSD_V2_ACL) || defined(CONFIG_NFSD_V3_ACL) - .pg_next = &nfsd_acl_program, -#endif .pg_prog = NFS_PROGRAM, /* program number */ .pg_nvers = NFSD_NRVERS, /* nr of entries in nfsd_version */ .pg_vers = nfsd_versions, /* version table */ @@ -115,6 +134,28 @@ struct svc_program nfsd_program = { }; +static void __init nfsd_prog_add(struct svc_program *new) +{ + struct svc_program *p = &nfsd_program; + + while (p->pg_next) + p = p->pg_next; + + p->pg_next = new; +} + +/* Dynamically initialize list of service programs */ +void __init nfsd_prog_init(void) +{ +#if defined(CONFIG_NFSD_V2_ACL) || defined(CONFIG_NFSD_V3_ACL) + nfsd_prog_add(&nfsd_acl_program); +#endif + +#ifdef CONFIG_NFSD_V3_XATTR + nfsd_prog_add(&nfsd_xattr_program); +#endif +} + u32 nfsd_supported_minorversion; int nfsd_vers(int vers, enum vers_op change) @@ -128,6 +169,10 @@ int nfsd_vers(int vers, enum vers_op change) if (vers < NFSD_ACL_NRVERS) nfsd_acl_versions[vers] = nfsd_acl_version[vers]; #endif +#ifdef CONFIG_NFSD_V3_XATTR + if (vers < NFSD_XATTR_NRVERS) + nfsd_xattr_versions[vers] = nfsd_xattr_version[vers]; +#endif break; case NFSD_CLEAR: nfsd_versions[vers] = NULL; @@ -135,6 +180,10 @@ int nfsd_vers(int vers, enum vers_op change) if (vers < NFSD_ACL_NRVERS) nfsd_acl_versions[vers] = NULL; #endif +#ifdef CONFIG_NFSD_V3_XATTR + if (vers < NFSD_XATTR_NRVERS) + nfsd_xattr_versions[vers] = NULL; +#endif break; case NFSD_TEST: return nfsd_versions[vers] != NULL; @@ -213,6 +262,11 @@ void nfsd_reset_versions(void) nfsd_acl_program.pg_vers[i] = nfsd_acl_version[i]; #endif +#ifdef CONFIG_NFSD_V3_XATTR + for (i = NFSD_XATTR_MINVERS; i < NFSD_XATTR_NRVERS; i++) + nfsd_xattr_program.pg_vers[i] = + nfsd_xattr_version[i]; +#endif } } diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index 3c11112..86309d2 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c @@ -454,8 +454,9 @@ out_nfserr: #if defined(CONFIG_NFSD_V2_ACL) || \ defined(CONFIG_NFSD_V3_ACL) || \ - defined(CONFIG_NFSD_V4) -static ssize_t nfsd_getxattr(struct dentry *dentry, char *key, void **buf) + defined(CONFIG_NFSD_V4) || \ + defined(CONFIG_NFSD_V3_XATTR) +ssize_t nfsd_getxattr(struct dentry *dentry, char *key, void **buf) { ssize_t buflen; ssize_t ret; diff --git a/fs/nfsd/vfs.h b/fs/nfsd/vfs.h index 217a62c..54079b2 100644 --- a/fs/nfsd/vfs.h +++ b/fs/nfsd/vfs.h @@ -99,4 +99,27 @@ struct posix_acl *nfsd_get_posix_acl(struct svc_fh *, int); int nfsd_set_posix_acl(struct svc_fh *, int, struct posix_acl *); #endif +#if defined(CONFIG_NFSD_V2_ACL) || \ + defined(CONFIG_NFSD_V3_ACL) || \ + defined(CONFIG_NFSD_V4) || \ + defined(CONFIG_NFSD_V3_XATTR) +ssize_t nfsd_getxattr(struct dentry *dentry, char *key, void **buf); +#endif + +#ifdef CONFIG_NFSD_V3_XATTR +extern struct svc_version nfsd_xattr_version3; + +/* + * Translation prefix for local storage of remote xattrs. This is currently + * hard-coded, but could be made a configurable per-export option. We're + * using the user namespace, which is widely supported by filesystems, and + * allows arbitrary manipulation. + */ +#define NFSD_XATTR_PREFIX "nfsd." +#define NFSD_XATTR_PREFIX_LEN (strlen(NFSD_XATTR_PREFIX)) + +#else +#define nfsd_xattr_version3 NULL +#endif + #endif /* LINUX_NFSD_VFS_H */ diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h index 7df980e..e6ccc60 100644 --- a/fs/nfsd/xdr3.h +++ b/fs/nfsd/xdr3.h @@ -119,6 +119,27 @@ struct nfsd3_setaclargs { struct posix_acl *acl_default; }; +struct nfsd3_getxattrargs { + struct svc_fh fh; + char * xattr_name; + unsigned int xattr_name_len; + unsigned int xattr_size_max; +}; + +struct nfsd3_setxattrargs { + struct svc_fh fh; + unsigned int xattr_flags; + char * xattr_name; + unsigned int xattr_name_len; + char * xattr_val; + int xattr_val_len; +}; + +struct nfsd3_listxattrargs { + struct svc_fh fh; + unsigned int xattr_list_max; +}; + struct nfsd3_attrstat { __be32 status; struct svc_fh fh; @@ -227,6 +248,25 @@ struct nfsd3_getaclres { struct posix_acl *acl_default; }; +struct nfsd3_getxattrres { + __be32 status; + struct svc_fh fh; + char * xattr_val; + unsigned int xattr_val_len; +}; + +struct nfsd3_setxattrres { + __be32 status; + struct svc_fh fh; +}; + +struct nfsd3_listxattrres { + __be32 status; + struct svc_fh fh; + char * xattr_list; + unsigned int xattr_list_len; +}; + /* dummy type for release */ struct nfsd3_fhandle_pair { __u32 dummy; @@ -247,6 +287,9 @@ union nfsd3_xdrstore { struct nfsd3_linkargs linkargs; struct nfsd3_symlinkargs symlinkargs; struct nfsd3_readdirargs readdirargs; + struct nfsd3_getxattrargs getxattrargs; + struct nfsd3_setxattrargs setxattrargs; + struct nfsd3_listxattrargs listxattrargs; struct nfsd3_diropres diropres; struct nfsd3_accessres accessres; struct nfsd3_readlinkres readlinkres; @@ -260,6 +303,9 @@ union nfsd3_xdrstore { struct nfsd3_pathconfres pathconfres; struct nfsd3_commitres commitres; struct nfsd3_getaclres getaclres; + struct nfsd3_getxattrres getxattrres; + struct nfsd3_setxattrres setxattrres; + struct nfsd3_listxattrres listxattrres; }; #define NFS3_SVC_XDRSIZE sizeof(union nfsd3_xdrstore) diff --git a/fs/xattr.c b/fs/xattr.c index 01bb813..ec4129b 100644 --- a/fs/xattr.c +++ b/fs/xattr.c @@ -46,9 +46,11 @@ xattr_permission(struct inode *inode, const char *name, int mask) return 0; /* - * The trusted.* namespace can only be accessed by a privileged user. + * The trusted.* and nfsd.* namespaces can only be accessed by a + * privileged user. */ - if (!strncmp(name, XATTR_TRUSTED_PREFIX, XATTR_TRUSTED_PREFIX_LEN)) + if (!strncmp(name, XATTR_TRUSTED_PREFIX, XATTR_TRUSTED_PREFIX_LEN) || + !strncmp(name, XATTR_NFSD_PREFIX, XATTR_NFSD_PREFIX_LEN)) return (capable(CAP_SYS_ADMIN) ? 0 : -EPERM); /* In user.* namespace, only regular files and directories can have diff --git a/include/linux/nfs.h b/include/linux/nfs.h index f387919..05fe996 100644 --- a/include/linux/nfs.h +++ b/include/linux/nfs.h @@ -110,6 +110,7 @@ NFSERR_FILE_OPEN = 10046, /* v4 */ NFSERR_ADMIN_REVOKED = 10047, /* v4 */ NFSERR_CB_PATH_DOWN = 10048, /* v4 */ + NFSERR_NODATA = 10049, /* v3 (XATTR) */ }; /* NFSv2 file types - beware, these are not the same in NFSv3 */ diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h index 77c2ae5..7bf0aa5 100644 --- a/include/linux/nfs_fs.h +++ b/include/linux/nfs_fs.h @@ -53,6 +53,7 @@ #include #include #include +#include #include @@ -294,6 +295,11 @@ static inline int nfs_server_capable(struct inode *inode, int cap) return NFS_SERVER(inode)->caps & cap; } +static inline bool nfs_server_xattrsec(struct inode *inode) +{ + return NFS_SERVER(inode)->flags & NFS_MOUNT_XATTRSEC; +} + static inline int NFS_USE_READDIRPLUS(struct inode *inode) { return test_bit(NFS_INO_ADVISE_RDPLUS, &NFS_I(inode)->flags); @@ -401,22 +407,6 @@ static inline struct rpc_cred *nfs_file_cred(struct file *file) } /* - * linux/fs/nfs/xattr.c - */ -#ifdef CONFIG_NFS_V3_ACL -extern ssize_t nfs3_listxattr(struct dentry *, char *, size_t); -extern ssize_t nfs3_getxattr(struct dentry *, const char *, void *, size_t); -extern int nfs3_setxattr(struct dentry *, const char *, - const void *, size_t, int); -extern int nfs3_removexattr (struct dentry *, const char *name); -#else -# define nfs3_listxattr NULL -# define nfs3_getxattr NULL -# define nfs3_setxattr NULL -# define nfs3_removexattr NULL -#endif - -/* * linux/fs/nfs/direct.c */ extern ssize_t nfs_direct_IO(int, struct kiocb *, const struct iovec *, loff_t, diff --git a/include/linux/nfs_fs_sb.h b/include/linux/nfs_fs_sb.h index d6e10a4..dde0d66 100644 --- a/include/linux/nfs_fs_sb.h +++ b/include/linux/nfs_fs_sb.h @@ -103,6 +103,7 @@ struct nfs_server { struct list_head master_link; /* link in master servers list */ struct rpc_clnt * client; /* RPC client handle */ struct rpc_clnt * client_acl; /* ACL RPC client handle */ + struct rpc_clnt * client_xattr; /* XATTR RPC client handle */ struct nlm_host *nlm_host; /* NLM client handle */ struct nfs_iostats __percpu *io_stats; /* I/O statistics */ struct backing_dev_info backing_dev_info; @@ -176,7 +177,7 @@ struct nfs_server { #define NFS_CAP_CTIME (1U << 12) #define NFS_CAP_MTIME (1U << 13) #define NFS_CAP_POSIX_LOCK (1U << 14) - +#define NFS_CAP_XATTR (1U << 15) /* maximum number of slots to use */ #define NFS4_MAX_SLOT_TABLE RPC_MAX_SLOT_TABLE diff --git a/include/linux/nfs_mount.h b/include/linux/nfs_mount.h index 4499016..c44355b 100644 --- a/include/linux/nfs_mount.h +++ b/include/linux/nfs_mount.h @@ -63,11 +63,13 @@ struct nfs_mount_data { #define NFS_MOUNT_SECFLAVOUR 0x2000 /* 5 */ #define NFS_MOUNT_NORDIRPLUS 0x4000 /* 5 */ #define NFS_MOUNT_UNSHARED 0x8000 /* 5 */ -#define NFS_MOUNT_FLAGMASK 0xFFFF +#define NFS_MOUNT_NOXATTR 0x10000 /* 6 */ +#define NFS_MOUNT_XATTRSEC 0x20000 /* 6 */ /* utilize security xattrs */ +#define NFS_MOUNT_FLAGMASK 0x3FFFF /* The following are for internal use only */ -#define NFS_MOUNT_LOOKUP_CACHE_NONEG 0x10000 -#define NFS_MOUNT_LOOKUP_CACHE_NONE 0x20000 -#define NFS_MOUNT_NORESVPORT 0x40000 +#define NFS_MOUNT_LOOKUP_CACHE_NONEG 0x100000 +#define NFS_MOUNT_LOOKUP_CACHE_NONE 0x200000 +#define NFS_MOUNT_NORESVPORT 0x400000 #endif diff --git a/include/linux/nfs_xattr.h b/include/linux/nfs_xattr.h new file mode 100644 index 0000000..98fdbed --- /dev/null +++ b/include/linux/nfs_xattr.h @@ -0,0 +1,21 @@ +/* + * Extended attribute protocol for NFSv3 (XATTR) + * + * + * Copyright (C) 2009 Red Hat, Inc., James Morris + * + */ +#ifndef __LINUX_NFS_XATTR_H +#define __LINUX_NFS_XATTR_H + +#include + +#define NFS_XATTR_PROGRAM 391063 /* TODO: find another value */ + +/* xattr procedure numbers */ +#define XATTRPROC3_GETXATTR 1 +#define XATTRPROC3_SETXATTR 2 +#define XATTRPROC3_LISTXATTR 3 +#define XATTRPROC3_RMXATTR 4 + +#endif /* __LINUX_NFS_XATTR_H */ diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h index 51914d7..b632fe6 100644 --- a/include/linux/nfs_xdr.h +++ b/include/linux/nfs_xdr.h @@ -2,6 +2,7 @@ #define _LINUX_NFS_XDR_H #include +#include #include /* @@ -514,6 +515,27 @@ struct nfs3_setaclargs { struct page ** pages; }; +struct nfs3_getxattrargs { + struct nfs_fh * fh; + const char * xattr_namespace; + const char * xattr_name; + unsigned int xattr_size_max; +}; + +struct nfs3_setxattrargs { + struct nfs_fh * fh; + unsigned int xattr_flags; + const char * xattr_namespace; + const char * xattr_name; + const char * xattr_val; + int xattr_val_len; +}; + +struct nfs3_listxattrargs { + struct nfs_fh * fh; + unsigned int xattr_list_max; +}; + struct nfs_diropok { struct nfs_fh * fh; struct nfs_fattr * fattr; @@ -646,6 +668,26 @@ struct nfs3_getaclres { struct posix_acl * acl_default; }; +struct nfs3_getxattrres { + struct nfs_fattr * fattr; + char * xattr_val; + int xattr_val_len; +}; + +/* + * Note: if we don't add any more fields, we can get rid of this struct and + * just use fattr in the calling code. + */ +struct nfs3_setxattrres { + struct nfs_fattr * fattr; +}; + +struct nfs3_listxattrres { + struct nfs_fattr * fattr; + char * xattr_list; + int xattr_list_len; +}; + #ifdef CONFIG_NFS_V4 typedef u64 clientid4; @@ -1079,4 +1121,7 @@ extern struct rpc_version nfs_version4; extern struct rpc_version nfsacl_version3; extern struct rpc_program nfsacl_program; +extern struct rpc_version nfs_xattr_version3; +extern struct rpc_program nfs_xattr_program; + #endif diff --git a/include/linux/sunrpc/svc.h b/include/linux/sunrpc/svc.h index 5a3085b..8bde5c1 100644 --- a/include/linux/sunrpc/svc.h +++ b/include/linux/sunrpc/svc.h @@ -371,7 +371,7 @@ struct svc_version { u32 vs_xdrsize; /* xdrsize needed for this version */ unsigned int vs_hidden : 1; /* Don't register with portmapper. - * Only used for nfsacl so far. */ + * Used for nfsacl and nfsxattr. */ /* Override dispatch function (e.g. when caching replies). * A return value of 0 means drop the request. diff --git a/include/linux/xattr.h b/include/linux/xattr.h index 0cfa1e9..da362a7 100644 --- a/include/linux/xattr.h +++ b/include/linux/xattr.h @@ -33,6 +33,9 @@ #define XATTR_USER_PREFIX "user." #define XATTR_USER_PREFIX_LEN (sizeof (XATTR_USER_PREFIX) - 1) +#define XATTR_NFSD_PREFIX "nfsd." +#define XATTR_NFSD_PREFIX_LEN (sizeof (XATTR_NFSD_PREFIX) - 1) + struct inode; struct dentry; diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 5c9f25b..3eaa13e 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -77,6 +77,7 @@ #include #include #include +#include /* for nfs_server_capable() */ #include "avc.h" #include "objsec.h" @@ -582,7 +583,7 @@ static int selinux_set_mnt_opts(struct super_block *sb, struct security_mnt_opts *opts) { const struct cred *cred = current_cred(); - int rc = 0, i; + int rc = 0, use_xattr = 0, i; struct superblock_security_struct *sbsec = sb->s_security; const char *name = sb->s_type->name; struct inode *inode = sbsec->sb->s_root->d_inode; @@ -697,11 +698,32 @@ static int selinux_set_mnt_opts(struct super_block *sb, if (strcmp(sb->s_type->name, "proc") == 0) sbsec->flags |= SE_SBPROC; + /* + * Special handling for NFSv3: if the xattrsec mount option was + * specified, check to see if the XATTR protocol is supported, and + * if so, require SECURITY_FS_USE_XATTR behavior. + */ + if (!strcmp(sb->s_type->name, "nfs")) { + if (nfs_server_xattrsec(inode)) { + if (nfs_server_capable(inode, NFS_CAP_XATTR)) + use_xattr = 1; + else { + printk(KERN_WARNING "SELinux: xattrsec " + "specified but XATTR unsupported\n"); + rc = -EOPNOTSUPP; + goto out; + } + } + } + /* Determine the labeling behavior to use for this filesystem type. */ - rc = security_fs_use((sbsec->flags & SE_SBPROC) ? "proc" : sb->s_type->name, &sbsec->behavior, &sbsec->sid); + rc = security_fs_use((sbsec->flags & SE_SBPROC) ? + "proc" : sb->s_type->name, &sbsec->behavior, + &sbsec->sid, use_xattr); if (rc) { - printk(KERN_WARNING "%s: security_fs_use(%s) returned %d\n", - __func__, sb->s_type->name, rc); + printk(KERN_WARNING "%s: security_fs_use(%s) (use_xattr=%d) " + "returned %d\n", __func__, sb->s_type->name, + use_xattr, rc); goto out; } @@ -1282,7 +1304,7 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent context, len); } dput(dentry); - if (rc < 0) { + if (rc <= 0) { if (rc != -ENODATA) { printk(KERN_WARNING "SELinux: %s: getxattr returned " "%d for dev=%s ino=%ld\n", __func__, diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index 1f7c249..b0dde76 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -163,7 +163,7 @@ int security_get_allow_unknown(void); #define SECURITY_FS_USE_MNTPOINT 6 /* use mountpoint labeling */ int security_fs_use(const char *fstype, unsigned int *behavior, - u32 *sid); + u32 *sid, int use_xattr); int security_genfs_sid(const char *fstype, char *name, u16 sclass, u32 *sid); diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index 1de60ce..17cb1b7 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -2238,11 +2238,12 @@ out: * @fstype: filesystem type * @behavior: labeling behavior * @sid: SID for filesystem (superblock) + * @use_xattr: use xattr labeling behavior for NFS */ int security_fs_use( const char *fstype, unsigned int *behavior, - u32 *sid) + u32 *sid, int use_xattr) { int rc = 0; struct ocontext *c; @@ -2251,8 +2252,19 @@ int security_fs_use( c = policydb.ocontexts[OCON_FSUSE]; while (c) { - if (strcmp(fstype, c->u.name) == 0) - break; + /* + * Without significant redesign, we need to add a special + * case for NFS here. TODO: consider using the new 'native' + * labeling behavior from LNFS. + */ + if (strcmp(fstype, c->u.name) == 0) { + if (strcmp(fstype, "nfs") == 0) { + if (use_xattr && + (c->v.behavior == SECURITY_FS_USE_XATTR)) + break; + } else + break; + } c = c->next; } @@ -2267,6 +2279,11 @@ int security_fs_use( } *sid = c->sid[0]; } else { + if (use_xattr) { + /* xattr required, must not fall back to genfs */ + rc = -EINVAL; + goto out; + } rc = security_genfs_sid(fstype, "/", SECCLASS_DIR, sid); if (rc) { *behavior = SECURITY_FS_USE_NONE;