create file_drop_write_access() helper
Subsystems (2)
PATCH 1/2 create file_drop_write_access() helper
These should fix the bug that Erez Zadok <[email protected]> reported:
Stopping RPC idmapd:
kernel: __fput() of writeable file with no mnt_want_write() kernel: WARNING: at fs/file_table.c:262 __fput() kernel: [<c010283e>] show_trace_log_lvl+0x12/0x25 kernel: [<c0103042>] show_trace+0xd/0x10
...
The actual bug was a missed mnt_want_write() when a filp was allocated with get_empty_filp() and then made writable. Using alloc_file(), instead, fixes that.
--
If someone decides to demote a file from r/w to just r/o, they can use this same code as __fput().
NFS does just that, and will use this in the next patch.
---
linux-2.6.git-dave/fs/file_table.c | 46 +++++++++++++++++++++----------- linux-2.6.git-dave/include/linux/file.h | 1 2 files changed, 32 insertions(+), 15 deletions(-)
fs/file_table.c renamed from fs/file_table.c~create-file_drop_write_access +31 -15
@@ -223,6 +223,34 @@ void fastcall fput(struct file *file)EXPORT_SYMBOL (fput); +/** +* drop_file_write_access - give up ability to write to a file +* @file: the file to which we will stop writing +* +* This is a central place which will give up the ability +* to write to @file, along with access to write through +* its vfsmount. +*/ +void drop_file_write_access (struct file *file) +{ +struct dentry *dentry = file->f_path.dentry; +struct vfsmount *mnt = file->f_path.mnt; +struct inode *inode = dentry->d_inode; + +put_write_access (inode); +if (!special_file (inode->i_mode)) { +if (file->f_mnt_write_state ==FILE_MNT_WRITE_TAKEN ) { +mnt_drop_write (mnt); + file->f_mnt_write_state |=FILE_MNT_WRITE_RELEASED ; + }else { +printk (KERN_WARNING "dropping write access on " +"file with no mnt_want_write()\n" ); +WARN_ON (1 ); + } + } +} +EXPORT_SYMBOL_GPL (drop_file_write_access); +/* __fput is called from task context when aio completion releases the last * last use of a struct file *. Do not use otherwise. */
@@ -248,21 +276,9 @@ void fastcall __fput(struct file *file)if (unlikely (S_ISCHR (inode->i_mode) && inode->i_cdev !=NULL ))cdev_put (inode->i_cdev);fops_put (file->f_op); -if (file->f_mode &FMODE_WRITE ) { -put_write_access (inode); -if (!special_file (inode->i_mode)) { -if (file->f_mnt_write_state ==FILE_MNT_WRITE_TAKEN ) { -mnt_drop_write (mnt); - file->f_mnt_write_state |= -FILE_MNT_WRITE_RELEASED ; - }else { -printk (KERN_WARNING "__fput() of writeable " -"file with no " -"mnt_want_write()\n" ); -WARN_ON (1 ); - } - } - } +if (file->f_mode &FMODE_WRITE ) +drop_file_write_access (file); +put_pid (file->f_owner.pid);file_kill (file); file->f_path.dentry =NULL ;
include/linux/file.h renamed from include/linux/file.h~create-file_drop_write_access +1 -0
@@ -61,6 +61,7 @@ extern struct kmem_cache *filp_cachep;extern void FASTCALL (__fput (struct file *));extern void FASTCALL (fput (struct file *)); +extern void drop_file_write_access (struct file *file);struct file_operations ;struct vfsmount ;
_
PATCH 2/2 fix up new filp allocators
Some new uses of get_empty_filp() have crept in, and are not properly taking mnt_want_write()s. This fixes them up.
We really need to kill get_empty_filp().
---
linux-2.6.git-dave/fs/anon_inodes.c | 16 ++++++---------- linux-2.6.git-dave/fs/file_table.c | 6 ++++++ linux-2.6.git-dave/fs/nfsd/nfs4state.c | 3 ++- linux-2.6.git-dave/fs/pipe.c | 17 +++++++---------- 4 files changed, 21 insertions(+), 21 deletions(-)
fs/anon_inodes.c renamed from fs/anon_inodes.c~fix-idmapd-bug +6 -10
@@ -81,13 +81,10 @@ int anon_inode_getfd(int *pfd, struct inif (IS_ERR (anon_inode_inode))return -ENODEV ; - file =get_empty_filp (); -if (!file) -return -ENFILE ; error =get_unused_fd ();if (error <0 ) - goto err_put_filp; +return error; fd = error;/*
@@ -114,14 +111,15 @@ int anon_inode_getfd(int *pfd, struct in dentry->d_flags &= ~DCACHE_UNHASHED ;d_instantiate (dentry, anon_inode_inode); - file->f_path.mnt =mntget (anon_inode_mnt); - file->f_path.dentry = dentry; + error = -ENFILE ; + file =alloc_file (anon_inode_mnt, dentry, +FMODE_READ |FMODE_WRITE , fops); +if (!file) + goto err_put_unused_fd; file->f_mapping = anon_inode_inode->i_mapping; file->f_pos =0 ; file->f_flags =O_RDWR ; - file->f_op = fops; - file->f_mode =FMODE_READ |FMODE_WRITE ; file->f_version =0 ; file->private_data = priv;
@@ -134,8 +132,6 @@ int anon_inode_getfd(int *pfd, struct in err_put_unused_fd:put_unused_fd (fd); -err_put_filp: -put_filp (file);return error; }EXPORT_SYMBOL_GPL (anon_inode_getfd);
fs/file_table.c renamed from fs/file_table.c~fix-idmapd-bug +6 -0
@@ -89,6 +89,12 @@ int proc_nr_files(ctl_table *table, int/* Find an unused file structure and return a pointer to it. * Returns NULL, if there are no more free file structures or * we run out of memory. +* +* Be very careful using this. You are responsible for +* getting write access to any mount that you might assign +* to this filp, if it is opened for write. If this is not +* done, you will imbalance int the mount's writer count +* and a warning at __fput() time. */ struct file *get_empty_filp (void ) {
fs/nfsd/nfs4state.c renamed from fs/nfsd/nfs4state.c~fix-idmapd-bug +2 -1
@@ -41,6 +41,7 @@#include <linux/sunrpc/svc.h> #include <linux/nfsd/nfsd.h> #include <linux/nfsd/cache.h> +#include <linux/file.h> #include <linux/mount.h> #include <linux/workqueue.h> #include <linux/smp_lock.h>
@@ -1303,7 +1304,7 @@ static inline void nfs4_file_downgrade(struct file *filp, unsigned int share_access) {if (share_access &NFS4_SHARE_ACCESS_WRITE ) { -put_write_access (filp->f_path.dentry->d_inode); +drop_file_write_access (filp); filp->f_mode = (filp->f_mode |FMODE_READ ) & ~FMODE_WRITE ; } }
fs/pipe.c renamed from fs/pipe.c~fix-idmapd-bug +7 -10
@@ -959,13 +959,10 @@ struct file *create_write_pipe(void)struct dentry *dentry;struct qstr name = { .name ="" }; - f =get_empty_filp (); -if (!f) -return ERR_PTR (-ENFILE ); err = -ENFILE ; inode =get_pipe_inode ();if (!inode) - goto err_file; + goto err; err = -ENOMEM ; dentry =d_alloc (pipe_mnt->mnt_sb->s_root, &name);
@@ -980,13 +977,14 @@ struct file *create_write_pipe(void)*/ dentry->d_flags &= ~DCACHE_UNHASHED ;d_instantiate (dentry, inode); - f->f_path.mnt =mntget (pipe_mnt); - f->f_path.dentry = dentry; + + f =alloc_file (pipe_mnt, dentry,FMODE_WRITE , &write_pipe_fops); + err = -ENFILE ; +if (!f) + goto err_inode; f->f_mapping = inode->i_mapping; f->f_flags =O_WRONLY ; - f->f_op = &write_pipe_fops; - f->f_mode =FMODE_WRITE ; f->f_version =0 ;return f;
@@ -994,8 +992,7 @@ struct file *create_write_pipe(void) err_inode:free_pipe_info (inode);iput (inode); - err_file: -put_filp (f); + err:return ERR_PTR (err); }
_
Re: [PATCH 2/2] fix up new filp allocators
Quoted 90 lines · Dave Hansen
Some new uses of
get_empty_filp()have crept in, and are not properly taking mnt_want_write()s. This fixes them up.We really need to kill
get_empty_filp().Signed-off-by Dave Hansen <[email protected]>---
linux-2.6.git-dave/fs/anon_inodes.c | 16 ++++++---------- linux-2.6.git-dave/fs/file_table.c | 6 ++++++ linux-2.6.git-dave/fs/nfsd/nfs4state.c | 3 ++- linux-2.6.git-dave/fs/pipe.c | 17 +++++++---------- 4 files changed, 21 insertions(+), 21 deletions(-)diff -puN
fs/anon_inodes.c~fix-idmapd-bugfs/anon_inodes.cfs/anon_inodes.c renamed from fs/anon_inodes.c~fix-idmapd-bug +6
-10@@ -81,13 +81,10 @@ int anon_inode_getfd(int *pfd, struct inif (IS_ERR (anon_inode_inode))return -ENODEV ; - file =get_empty_filp (); -if (!file) -return -ENFILE ; error =get_unused_fd ();if (error <0 ) - goto err_put_filp; +return error; fd = error;/* @@ -114,14 +111,15 @@ int anon_inode_getfd(int *pfd, struct in dentry->d_flags &= ~DCACHE_UNHASHED ;d_instantiate (dentry, anon_inode_inode); - file->f_path.mnt =mntget (anon_inode_mnt); - file->f_path.dentry = dentry; + error = -ENFILE ; + file =alloc_file (anon_inode_mnt, dentry, +FMODE_READ |FMODE_WRITE , fops); +if (!file) + goto err_put_unused_fd; file->f_mapping = anon_inode_inode->i_mapping; file->f_pos =0 ; file->f_flags =O_RDWR ; - file->f_op = fops; - file->f_mode =FMODE_READ |FMODE_WRITE ; file->f_version =0 ; file->private_data = priv;@@ -134,8 +132,6 @@ int anon_inode_getfd(int *pfd, struct in err_put_unused_fd:put_unused_fd (fd); -err_put_filp: -put_filp (file);return error; }EXPORT_SYMBOL_GPL (anon_inode_getfd);diff -puN
fs/file_table.c~fix-idmapd-bugfs/file_table.cfs/file_table.c renamed from fs/file_table.c~fix-idmapd-bug +6
-0@@ -89,6 +89,12 @@ int proc_nr_files(ctl_table *table, int/* Find an unused file structure and return a pointer to it. * Returns NULL, if there are no more free file structures or * we run out of memory. +* +* Be very careful using this. You are responsible for +* getting write access to any mount that you might assign +* to this filp, if it is opened for write. If this is not +* done, you will imbalance int the mount's writer count +* and a warning at __fput() time. */ struct file *get_empty_filp (void ) {diff -puN
fs/nfsd/nfs4state.c~fix-idmapd-bugfs/nfsd/nfs4state.cfs/nfsd/nfs4state.c renamed from fs/nfsd/nfs4state.c~fix-idmapd-bug +1
-0@@ -41,6 +41,7 @@#include <linux/sunrpc/svc.h> #include <linux/nfsd/nfsd.h> #include <linux/nfsd/cache.h> +#include <linux/file.h> #include <linux/mount.h> #include <linux/workqueue.h> #include <linux/smp_lock.h>
fs/nfsd/nfs4state.c static inline void · line 1310
Quoted 8 lines
fs/nfsd/nfs4state.c renamed from fs/nfsd/nfs4state.c~fix-idmapd-bug +1
-1@@ -1303,7 +1304,7 @@ static inline void nfs4_file_downgrade(struct file *filp, unsigned int share_access) {if (share_access &NFS4_SHARE_ACCESS_WRITE ) { -put_write_access (filp->f_path.dentry->d_inode); +drop_file_write_access (filp); filp->f_mode = (filp->f_mode |FMODE_READ ) & ~FMODE_WRITE ; } }
Hmm... The NFS server may also try to 'upgrade' an open file request to a read/write request whenever the client issues a new OPEN request for WRITE using the same open_owner.
Quoted 38 lines
diff -puN
fs/pipe.c~fix-idmapd-bugfs/pipe.cfs/pipe.c renamed from fs/pipe.c~fix-idmapd-bug +6
-8@@ -959,13 +959,10 @@ struct file *create_write_pipe(void)struct dentry *dentry;struct qstr name = { .name ="" }; - f =get_empty_filp (); -if (!f) -return ERR_PTR (-ENFILE ); err = -ENFILE ; inode =get_pipe_inode ();if (!inode) - goto err_file; + goto err; err = -ENOMEM ; dentry =d_alloc (pipe_mnt->mnt_sb->s_root, &name);@@ -980,13 +977,14 @@ struct file *create_write_pipe(void)*/ dentry->d_flags &= ~DCACHE_UNHASHED ;d_instantiate (dentry, inode); - f->f_path.mnt =mntget (pipe_mnt); - f->f_path.dentry = dentry; + + f =alloc_file (pipe_mnt, dentry,FMODE_WRITE , &write_pipe_fops); + err = -ENFILE ; +if (!f) + goto err_inode; f->f_mapping = inode->i_mapping; f->f_flags =O_WRONLY ; - f->f_op = &write_pipe_fops; - f->f_mode =FMODE_WRITE ; f->f_version =0 ;return f;
fs/pipe.c struct file *create_write_pipe(void) · line 997
Quoted 8 lines
fs/pipe.c renamed from fs/pipe.c~fix-idmapd-bug +1
-2@@ -994,8 +992,7 @@ struct file *create_write_pipe(void) err_inode:free_pipe_info (inode);iput (inode); - err_file: -put_filp (f); + err:return ERR_PTR (err); }
Does RPC idmapd open a regular pipe too? I thought it only used rpc_pipefs?
Trond
Re: [PATCH 2/2] fix up new filp allocators
Quoted 12 lines · Trond Myklebust
Quoted 12 lines
#include <
linux/sunrpc/svc.h>
#include <linux/nfsd/nfsd.h>
#include <linux/nfsd/cache.h>fs/nfsd/nfs4state.c renamed from fs/nfsd/nfs4state.c~fix-idmapd-bug +1
-1+#include <linux/file.h> #include <linux/mount.h> #include <linux/workqueue.h> #include <linux/smp_lock.h> @@ -1303,7 +1304,7 @@ static inline void nfs4_file_downgrade(struct file *filp, unsigned int share_access) {if (share_access &NFS4_SHARE_ACCESS_WRITE ) { -put_write_access (filp->f_path.dentry->d_inode);
Quoted 8 lines
fs/nfsd/nfs4state.c static inline void · line 1310
Quoted 4 lines
fs/nfsd/nfs4state.c renamed from fs/nfsd/nfs4state.c~fix-idmapd-bug +1
-0+drop_file_write_access (filp); filp->f_mode = (filp->f_mode |FMODE_READ ) & ~FMODE_WRITE ; } }Hmm... The NFS server may also try to 'upgrade' an open file request to a read/write request whenever the client issues a new OPEN request for WRITE using the same
open_owner.
Can you point me to some code? I'll try and go fix it up.
Quoted 36 lines
Quoted 36 lines
diff -puN
fs/pipe.c~fix-idmapd-bugfs/pipe.cfs/pipe.c renamed from fs/pipe.c~fix-idmapd-bug +6
-8@@ -959,13 +959,10 @@ struct file *create_write_pipe(void)struct dentry *dentry;struct qstr name = { .name ="" }; - f =get_empty_filp (); -if (!f) -return ERR_PTR (-ENFILE ); err = -ENFILE ; inode =get_pipe_inode ();if (!inode) - goto err_file; + goto err; err = -ENOMEM ; dentry =d_alloc (pipe_mnt->mnt_sb->s_root, &name);@@ -980,13 +977,14 @@ struct file *create_write_pipe(void)*/ dentry->d_flags &= ~DCACHE_UNHASHED ;d_instantiate (dentry, inode); - f->f_path.mnt =mntget (pipe_mnt); - f->f_path.dentry = dentry; + + f =alloc_file (pipe_mnt, dentry,FMODE_WRITE , &write_pipe_fops); + err = -ENFILE ; +if (!f) + goto err_inode; f->f_mapping = inode->i_mapping; f->f_flags =O_WRONLY ; - f->f_op = &write_pipe_fops; - f->f_mode =FMODE_WRITE ; f->f_version =0 ;
Quoted 13 lines
fs/pipe.c struct file *create_write_pipe(void) · line 997
Quoted 10 lines
fs/pipe.c renamed from fs/pipe.c~fix-idmapd-bug +1
-2return f;@@ -994,8 +992,7 @@ struct file *create_write_pipe(void) err_inode:free_pipe_info (inode);iput (inode); - err_file: -put_filp (f); + err:return ERR_PTR (err); }Does RPC idmapd open a regular pipe too? I thought it only used
rpc_pipefs?
No. I the the actual bug triggered by idmapd was from an eventpoll filp that had been acquired through anon_inode_getfd(). I just noticed the pipe code here could be another potential bug of the same type.
-- Dave
Re: [PATCH 2/2] fix up new filp allocators
Quoted 22 lines · Dave Hansen
Quoted 12 lines · Trond Myklebust
Quoted 12 lines
#include <
linux/sunrpc/svc.h>
#include <linux/nfsd/nfsd.h>
#include <linux/nfsd/cache.h>fs/nfsd/nfs4state.c renamed from fs/nfsd/nfs4state.c~fix-idmapd-bug +1
-1+#include <linux/file.h> #include <linux/mount.h> #include <linux/workqueue.h> #include <linux/smp_lock.h> @@ -1303,7 +1304,7 @@ static inline void nfs4_file_downgrade(struct file *filp, unsigned int share_access) {if (share_access &NFS4_SHARE_ACCESS_WRITE ) { -put_write_access (filp->f_path.dentry->d_inode);Quoted 8 lines
fs/nfsd/nfs4state.c static inline void · line 1310
Quoted 4 lines
fs/nfsd/nfs4state.c renamed from fs/nfsd/nfs4state.c~fix-idmapd-bug +1
-0+drop_file_write_access (filp); filp->f_mode = (filp->f_mode |FMODE_READ ) & ~FMODE_WRITE ; } }Hmm... The NFS server may also try to 'upgrade' an open file request to a read/write request whenever the client issues a new OPEN request for WRITE using the same
open_owner.
Quoted 1 line
Can you point me to some code? I'll try and go fix it up.
See fs/nfsd/nfs4state.c:nfs4_upgrade_open().
I suspect that there are other reasons why what nfsd is doing here is a bad idea, and that we should really be getting a new file descriptor. But I haven't figured out yet what to do instead.
--b.
Quoted 10 lines · Dave Hansen
These should fix the bug that Erez Zadok <[email protected]> reported:
Stopping RPC idmapd:
kernel: __fput() of writeable file with no mnt_want_write() kernel: WARNING: at fs/file_table.c:262 __fput() kernel: [<c010283e>] show_trace_log_lvl+0x12/0x25 kernel: [<c0103042>] show_trace+0xd/0x10
Quoted 6 lines
...
The actual bug was a missed
mnt_want_write()when a filp was allocated withget_empty_filp()and then made writable. Usingalloc_file(), instead, fixes that.
[...]
I can confirm that this patch fixes the bug I reported earlier today.
Thanks,
Erez.