From f9c3a3802119a2d30f3e4a69aef30a81e09d0209 Mon Sep 17 00:00:00 2001 From: Eric Paris Date: Wed, 5 Mar 2008 14:20:18 -0500 Subject: NFS: use new LSM interfaces to explicitly set mount options NFS and SELinux worked together previously because SELinux had NFS specific knowledge built in. This design was approved by both groups back in 2004 but the recent NFS changes to use nfs_parsed_mount_data and the usage of nfs_clone_mount_data showed this to be a poor fragile solution. This patch fixes the NFS functionality regression by making use of the new LSM interfaces to allow an FS to explicitly set its own mount options. The explicit setting of mount options is done in the nfs get_sb functions which are called before the generic vfs hooks try to set mount options for filesystems which use text mount data. This does not currently support NFSv4 as that functionality did not exist in previous kernels and thus there is no regression. I will be adding the needed code, which I believe to be the exact same as the v3 code, in nfs4_get_sb for 2.6.26. Signed-off-by: Eric Paris Acked-by: Trond Myklebust Signed-off-by: James Morris --- fs/nfs/internal.h | 3 +++ fs/nfs/super.c | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 65 insertions(+), 2 deletions(-) (limited to 'fs/nfs') diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h index 0f5619611b8..931992763e6 100644 --- a/fs/nfs/internal.h +++ b/fs/nfs/internal.h @@ -3,6 +3,7 @@ */ #include +#include struct nfs_string; @@ -57,6 +58,8 @@ struct nfs_parsed_mount_data { char *export_path; int protocol; } nfs_server; + + struct security_mnt_opts lsm_opts; }; /* client.c */ diff --git a/fs/nfs/super.c b/fs/nfs/super.c index 1fb38184365..fcf4b982c88 100644 --- a/fs/nfs/super.c +++ b/fs/nfs/super.c @@ -684,8 +684,9 @@ static void nfs_parse_server_address(char *value, static int nfs_parse_mount_options(char *raw, struct nfs_parsed_mount_data *mnt) { - char *p, *string; + char *p, *string, *secdata; unsigned short port = 0; + int rc; if (!raw) { dfprintk(MOUNT, "NFS: mount options string was NULL.\n"); @@ -693,6 +694,20 @@ static int nfs_parse_mount_options(char *raw, } dfprintk(MOUNT, "NFS: nfs mount opts='%s'\n", raw); + secdata = alloc_secdata(); + if (!secdata) + goto out_nomem; + + rc = security_sb_copy_data(raw, secdata); + if (rc) + goto out_security_failure; + + rc = security_sb_parse_opts_str(secdata, &mnt->lsm_opts); + if (rc) + goto out_security_failure; + + free_secdata(secdata); + while ((p = strsep(&raw, ",")) != NULL) { substring_t args[MAX_OPT_ARGS]; int option, token; @@ -1042,7 +1057,10 @@ static int nfs_parse_mount_options(char *raw, out_nomem: printk(KERN_INFO "NFS: not enough memory to parse option\n"); return 0; - +out_security_failure: + free_secdata(secdata); + printk(KERN_INFO "NFS: security options invalid: %d\n", rc); + return 0; out_unrec_vers: printk(KERN_INFO "NFS: unrecognized NFS version number\n"); return 0; @@ -1214,6 +1232,33 @@ static int nfs_validate_mount_data(void *options, args->namlen = data->namlen; args->bsize = data->bsize; args->auth_flavors[0] = data->pseudoflavor; + + /* + * The legacy version 6 binary mount data from userspace has a + * field used only to transport selinux information into the + * the kernel. To continue to support that functionality we + * have a touch of selinux knowledge here in the NFS code. The + * userspace code converted context=blah to just blah so we are + * converting back to the full string selinux understands. + */ + if (data->context[0]){ +#ifdef CONFIG_SECURITY_SELINUX + int rc; + char *opts_str = kmalloc(sizeof(data->context) + 8, GFP_KERNEL); + if (!opts_str) + return -ENOMEM; + strcpy(opts_str, "context="); + data->context[NFS_MAX_CONTEXT_LEN] = '\0'; + strcat(opts_str, &data->context[0]); + rc = security_sb_parse_opts_str(opts_str, &args->lsm_opts); + kfree(opts_str); + if (rc) + return rc; +#else + return -EINVAL; +#endif + } + break; default: { unsigned int len; @@ -1476,6 +1521,8 @@ static int nfs_get_sb(struct file_system_type *fs_type, }; int error; + security_init_mnt_opts(&data.lsm_opts); + /* Validate the mount data */ error = nfs_validate_mount_data(raw_data, &data, &mntfh, dev_name); if (error < 0) @@ -1515,6 +1562,10 @@ static int nfs_get_sb(struct file_system_type *fs_type, goto error_splat_super; } + error = security_sb_set_mnt_opts(s, &data.lsm_opts); + if (error) + goto error_splat_root; + s->s_flags |= MS_ACTIVE; mnt->mnt_sb = s; mnt->mnt_root = mntroot; @@ -1523,12 +1574,15 @@ static int nfs_get_sb(struct file_system_type *fs_type, out: kfree(data.nfs_server.hostname); kfree(data.mount_server.hostname); + security_free_mnt_opts(&data.lsm_opts); return error; out_err_nosb: nfs_free_server(server); goto out; +error_splat_root: + dput(mntroot); error_splat_super: up_write(&s->s_umount); deactivate_super(s); @@ -1608,6 +1662,9 @@ static int nfs_xdev_get_sb(struct file_system_type *fs_type, int flags, mnt->mnt_sb = s; mnt->mnt_root = mntroot; + /* clone any lsm security options from the parent to the new sb */ + security_sb_clone_mnt_opts(data->sb, s); + dprintk("<-- nfs_xdev_get_sb() = 0\n"); return 0; @@ -1850,6 +1907,8 @@ static int nfs4_get_sb(struct file_system_type *fs_type, }; int error; + security_init_mnt_opts(&data.lsm_opts); + /* Validate the mount data */ error = nfs4_validate_mount_data(raw_data, &data, dev_name); if (error < 0) @@ -1898,6 +1957,7 @@ out: kfree(data.client_address); kfree(data.nfs_server.export_path); kfree(data.nfs_server.hostname); + security_free_mnt_opts(&data.lsm_opts); return error; out_free: -- cgit v1.2.3 From af1b8c2ff7c337c4e96db12d6b7b61eaa91aa069 Mon Sep 17 00:00:00 2001 From: Trond Myklebust Date: Mon, 25 Feb 2008 15:56:29 -0800 Subject: NFS: Fix an f_mode/f_flags confusion in fs/nfs/write.c O_SYNC is stored in filp->f_flags. Thanks to Al Viro for pointing out the bug. Signed-off-by: Trond Myklebust --- fs/nfs/write.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'fs/nfs') diff --git a/fs/nfs/write.c b/fs/nfs/write.c index f55c437124a..80c61fdb272 100644 --- a/fs/nfs/write.c +++ b/fs/nfs/write.c @@ -734,7 +734,7 @@ int nfs_updatepage(struct file *file, struct page *page, */ if (nfs_write_pageuptodate(page, inode) && inode->i_flock == NULL && - !(file->f_mode & O_SYNC)) { + !(file->f_flags & O_SYNC)) { count = max(count + offset, nfs_page_length(page)); offset = 0; } -- cgit v1.2.3 From c37dcd334c0b0a46a90cfa13b9f69e2aaa89bc09 Mon Sep 17 00:00:00 2001 From: Trond Myklebust Date: Thu, 6 Mar 2008 12:34:50 -0500 Subject: NFS: Fix the fsid revalidation in nfs_update_inode() When we detect that we've crossed a mountpoint on the remote server, we must take care not to use that inode to revalidate the fsid on our current superblock. To do so, we label the inode as a remote mountpoint, and check for that in nfs_update_inode(). Signed-off-by: Trond Myklebust --- fs/nfs/inode.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'fs/nfs') diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c index 966a8850aa3..a4c7cf2bff3 100644 --- a/fs/nfs/inode.c +++ b/fs/nfs/inode.c @@ -299,6 +299,7 @@ nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr) else inode->i_op = &nfs_mountpoint_inode_operations; inode->i_fop = NULL; + set_bit(NFS_INO_MOUNTPOINT, &nfsi->flags); } } else if (S_ISLNK(inode->i_mode)) inode->i_op = &nfs_symlink_inode_operations; @@ -1003,8 +1004,9 @@ static int nfs_update_inode(struct inode *inode, struct nfs_fattr *fattr) server = NFS_SERVER(inode); /* Update the fsid? */ - if (S_ISDIR(inode->i_mode) - && !nfs_fsid_equal(&server->fsid, &fattr->fsid)) + if (S_ISDIR(inode->i_mode) && + !nfs_fsid_equal(&server->fsid, &fattr->fsid) && + !test_bit(NFS_INO_MOUNTPOINT, &nfsi->flags)) server->fsid = fattr->fsid; /* -- cgit v1.2.3 From 4e99a1ff3410c627a428d5ddb6cd2e7bc908a486 Mon Sep 17 00:00:00 2001 From: Trond Myklebust Date: Thu, 6 Mar 2008 12:34:59 -0500 Subject: NFS: Fix dentry revalidation for NFSv4 referrals and mountpoint crossings As long as the directory contents haven't changed, we should just let the path walk proceed to cross the mountpoint. Apart from being an optimisation in the case of 'nohide' mountpoint traversals, it also fixes an issue with referrals: referral inodes don't have valid filehandles, so calling nfs_revalidate_inode() on them is a bug. Signed-off-by: Trond Myklebust --- fs/nfs/dir.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'fs/nfs') diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index ae04892a5e5..6cea7479c5b 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -710,6 +710,8 @@ int nfs_lookup_verify_inode(struct inode *inode, struct nameidata *nd) { struct nfs_server *server = NFS_SERVER(inode); + if (test_bit(NFS_INO_MOUNTPOINT, &NFS_I(inode)->flags)) + return 0; if (nd != NULL) { /* VFS wants an on-the-wire revalidation */ if (nd->flags & LOOKUP_REVAL) -- cgit v1.2.3