Commit 63a0d3c5 authored by Alexander Viro's avatar Alexander Viro Committed by Linus Torvalds

[PATCH] ->setattr() locking changes

Take ->i_sem in all callers of notify_change().
parent 27f7db2c
...@@ -65,7 +65,7 @@ rename: no yes (all) (see below) ...@@ -65,7 +65,7 @@ rename: no yes (all) (see below)
readlink: no no readlink: no no
follow_link: no no follow_link: no no
truncate: no yes (see below) truncate: no yes (see below)
setattr: yes if ATTR_SIZE setattr: no yes
permission: yes no permission: yes no
getattr: (see below) getattr: (see below)
revalidate: no (see below) revalidate: no (see below)
......
...@@ -116,3 +116,10 @@ FS_LITTER is gone - just remove it from fs_flags. ...@@ -116,3 +116,10 @@ FS_LITTER is gone - just remove it from fs_flags.
FS_SINGLE is gone (actually, that had happened back when ->get_sb() FS_SINGLE is gone (actually, that had happened back when ->get_sb()
went in - and hadn't been documented ;-/). Just remove it from fs_flags went in - and hadn't been documented ;-/). Just remove it from fs_flags
(and see ->get_sb() entry for other actions). (and see ->get_sb() entry for other actions).
---
[mandatory]
->setattr() is called without BKL now. Caller _always_ holds ->i_sem, so
watch for ->i_sem-grabbing code that might be used by your ->setattr().
Callers of notify_change() need ->i_sem now.
...@@ -119,6 +119,7 @@ static int setattr_mask(unsigned int ia_valid) ...@@ -119,6 +119,7 @@ static int setattr_mask(unsigned int ia_valid)
int notify_change(struct dentry * dentry, struct iattr * attr) int notify_change(struct dentry * dentry, struct iattr * attr)
{ {
struct inode *inode = dentry->d_inode; struct inode *inode = dentry->d_inode;
mode_t mode = inode->i_mode;
int error; int error;
time_t now = CURRENT_TIME; time_t now = CURRENT_TIME;
unsigned int ia_valid = attr->ia_valid; unsigned int ia_valid = attr->ia_valid;
...@@ -131,8 +132,25 @@ int notify_change(struct dentry * dentry, struct iattr * attr) ...@@ -131,8 +132,25 @@ int notify_change(struct dentry * dentry, struct iattr * attr)
attr->ia_atime = now; attr->ia_atime = now;
if (!(ia_valid & ATTR_MTIME_SET)) if (!(ia_valid & ATTR_MTIME_SET))
attr->ia_mtime = now; attr->ia_mtime = now;
if (ia_valid & ATTR_KILL_SUID) {
if (mode & S_ISUID) {
if (!ia_valid & ATTR_MODE) {
ia_valid = attr->ia_valid |= ATTR_MODE;
attr->ia_mode = inode->i_mode;
}
attr->ia_mode &= ~S_ISUID;
}
}
if (ia_valid & ATTR_KILL_SGID) {
if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) {
if (!ia_valid & ATTR_MODE) {
ia_valid = attr->ia_valid |= ATTR_MODE;
attr->ia_mode = inode->i_mode;
}
attr->ia_mode &= ~S_ISGID;
}
}
down(&inode->i_sem);
if (inode->i_op && inode->i_op->setattr) if (inode->i_op && inode->i_op->setattr)
error = inode->i_op->setattr(dentry, attr); error = inode->i_op->setattr(dentry, attr);
else { else {
...@@ -145,7 +163,6 @@ int notify_change(struct dentry * dentry, struct iattr * attr) ...@@ -145,7 +163,6 @@ int notify_change(struct dentry * dentry, struct iattr * attr)
error = inode_setattr(inode, attr); error = inode_setattr(inode, attr);
} }
} }
up(&inode->i_sem);
if (!error) { if (!error) {
unsigned long dn_mask = setattr_mask(ia_valid); unsigned long dn_mask = setattr_mask(ia_valid);
if (dn_mask) if (dn_mask)
......
...@@ -429,7 +429,7 @@ int shrink_dqcache_memory(int priority, unsigned int gfp_mask) ...@@ -429,7 +429,7 @@ int shrink_dqcache_memory(int priority, unsigned int gfp_mask)
count = nr_free_dquots / priority; count = nr_free_dquots / priority;
prune_dqcache(count); prune_dqcache(count);
unlock_kernel(); unlock_kernel();
return kmem_cache_shrink_nr(dquot_cachep); return kmem_cache_shrink(dquot_cachep);
} }
/* NOTE: If you change this function please check whether dqput_blocks() works right... */ /* NOTE: If you change this function please check whether dqput_blocks() works right... */
......
...@@ -1086,7 +1086,8 @@ int fat_notify_change(struct dentry * dentry, struct iattr * attr) ...@@ -1086,7 +1086,8 @@ int fat_notify_change(struct dentry * dentry, struct iattr * attr)
error = 0; error = 0;
goto out; goto out;
} }
if( error = inode_setattr(inode, attr) ) error = inode_setattr(inode, attr);
if (error)
goto out; goto out;
if (S_ISDIR(inode->i_mode)) if (S_ISDIR(inode->i_mode))
......
...@@ -365,11 +365,9 @@ int hpfs_unlink(struct inode *dir, struct dentry *dentry) ...@@ -365,11 +365,9 @@ int hpfs_unlink(struct inode *dir, struct dentry *dentry)
goto ret; goto ret;
} }
/*printk("HPFS: truncating file before delete.\n");*/ /*printk("HPFS: truncating file before delete.\n");*/
down(&inode->i_sem);
newattrs.ia_size = 0; newattrs.ia_size = 0;
newattrs.ia_valid = ATTR_SIZE | ATTR_CTIME; newattrs.ia_valid = ATTR_SIZE | ATTR_CTIME;
err = notify_change(dentry, &newattrs); err = notify_change(dentry, &newattrs);
up(&inode->i_sem);
put_write_access(inode); put_write_access(inode);
if (err) if (err)
goto ret; goto ret;
......
...@@ -43,6 +43,7 @@ ...@@ -43,6 +43,7 @@
#include <linux/pagemap.h> #include <linux/pagemap.h>
#include <linux/crc32.h> #include <linux/crc32.h>
#include <linux/jffs2.h> #include <linux/jffs2.h>
#include <linux/smp_lock.h>
#include "nodelist.h" #include "nodelist.h"
extern int generic_file_open(struct inode *, struct file *) __attribute__((weak)); extern int generic_file_open(struct inode *, struct file *) __attribute__((weak));
......
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/vmalloc.h> #include <linux/vmalloc.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/smp_lock.h>
#include <linux/ncp_fs.h> #include <linux/ncp_fs.h>
......
...@@ -264,6 +264,7 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap, ...@@ -264,6 +264,7 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap,
if (err) if (err)
goto out_nfserr; goto out_nfserr;
size_change = 1;
err = locks_verify_truncate(inode, NULL, iap->ia_size); err = locks_verify_truncate(inode, NULL, iap->ia_size);
if (err) { if (err) {
put_write_access(inode); put_write_access(inode);
...@@ -279,35 +280,24 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap, ...@@ -279,35 +280,24 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap,
} }
/* Revoke setuid/setgid bit on chown/chgrp */ /* Revoke setuid/setgid bit on chown/chgrp */
if ((iap->ia_valid & ATTR_UID) && (imode & S_ISUID) if ((iap->ia_valid & ATTR_UID) && iap->ia_uid != inode->i_uid)
&& iap->ia_uid != inode->i_uid) { iap->ia_valid |= ATTR_KILL_SUID;
iap->ia_valid |= ATTR_MODE; if ((iap->ia_valid & ATTR_GID) && iap->ia_gid != inode->i_gid)
iap->ia_mode = imode &= ~S_ISUID; iap->ia_valid |= ATTR_KILL_SGID;
}
if ((iap->ia_valid & ATTR_GID) && (imode & S_ISGID)
&& iap->ia_gid != inode->i_gid) {
iap->ia_valid |= ATTR_MODE;
iap->ia_mode = imode &= ~S_ISGID;
}
/* Change the attributes. */ /* Change the attributes. */
iap->ia_valid |= ATTR_CTIME; iap->ia_valid |= ATTR_CTIME;
if (iap->ia_valid & ATTR_SIZE) {
fh_lock(fhp);
size_change = 1;
}
err = nfserr_notsync; err = nfserr_notsync;
if (!check_guard || guardtime == inode->i_ctime) { if (!check_guard || guardtime == inode->i_ctime) {
fh_lock(fhp);
err = notify_change(dentry, iap); err = notify_change(dentry, iap);
err = nfserrno(err); err = nfserrno(err);
}
if (size_change) {
fh_unlock(fhp); fh_unlock(fhp);
put_write_access(inode);
} }
if (size_change)
put_write_access(inode);
if (!err) if (!err)
if (EX_ISSYNC(fhp->fh_export)) if (EX_ISSYNC(fhp->fh_export))
write_inode_now(inode, 1); write_inode_now(inode, 1);
...@@ -725,10 +715,11 @@ nfsd_write(struct svc_rqst *rqstp, struct svc_fh *fhp, loff_t offset, ...@@ -725,10 +715,11 @@ nfsd_write(struct svc_rqst *rqstp, struct svc_fh *fhp, loff_t offset,
/* clear setuid/setgid flag after write */ /* clear setuid/setgid flag after write */
if (err >= 0 && (inode->i_mode & (S_ISUID | S_ISGID))) { if (err >= 0 && (inode->i_mode & (S_ISUID | S_ISGID))) {
struct iattr ia; struct iattr ia;
ia.ia_valid = ATTR_KILL_SUID | ATTR_KILL_SGID;
ia.ia_valid = ATTR_MODE; down(&inode->i_sem);
ia.ia_mode = inode->i_mode & ~(S_ISUID | S_ISGID);
notify_change(dentry, &ia); notify_change(dentry, &ia);
up(&inode->i_sem);
} }
if (err >= 0 && stable) { if (err >= 0 && stable) {
...@@ -1157,7 +1148,9 @@ nfsd_symlink(struct svc_rqst *rqstp, struct svc_fh *fhp, ...@@ -1157,7 +1148,9 @@ nfsd_symlink(struct svc_rqst *rqstp, struct svc_fh *fhp,
iap->ia_valid |= ATTR_CTIME; iap->ia_valid |= ATTR_CTIME;
iap->ia_mode = (iap->ia_mode&S_IALLUGO) iap->ia_mode = (iap->ia_mode&S_IALLUGO)
| S_IFLNK; | S_IFLNK;
down(&dentry->d_inode->i_sem);
err = notify_change(dnew, iap); err = notify_change(dnew, iap);
up(&dentry->d_inode->i_sem);
if (!err && EX_ISSYNC(fhp->fh_export)) if (!err && EX_ISSYNC(fhp->fh_export))
write_inode_now(dentry->d_inode, 1); write_inode_now(dentry->d_inode, 1);
} }
......
...@@ -73,6 +73,7 @@ asmlinkage long sys_fstatfs(unsigned int fd, struct statfs * buf) ...@@ -73,6 +73,7 @@ asmlinkage long sys_fstatfs(unsigned int fd, struct statfs * buf)
int do_truncate(struct dentry *dentry, loff_t length) int do_truncate(struct dentry *dentry, loff_t length)
{ {
int err;
struct iattr newattrs; struct iattr newattrs;
/* Not pretty: "inode->i_size" shouldn't really be signed. But it is. */ /* Not pretty: "inode->i_size" shouldn't really be signed. But it is. */
...@@ -81,7 +82,10 @@ int do_truncate(struct dentry *dentry, loff_t length) ...@@ -81,7 +82,10 @@ int do_truncate(struct dentry *dentry, loff_t length)
newattrs.ia_size = length; newattrs.ia_size = length;
newattrs.ia_valid = ATTR_SIZE | ATTR_CTIME; newattrs.ia_valid = ATTR_SIZE | ATTR_CTIME;
return notify_change(dentry, &newattrs); down(&dentry->d_inode->i_sem);
err = notify_change(dentry, &newattrs);
up(&dentry->d_inode->i_sem);
return err;
} }
static inline long do_sys_truncate(const char * path, loff_t length) static inline long do_sys_truncate(const char * path, loff_t length)
...@@ -255,7 +259,9 @@ asmlinkage long sys_utime(char * filename, struct utimbuf * times) ...@@ -255,7 +259,9 @@ asmlinkage long sys_utime(char * filename, struct utimbuf * times)
(error = permission(inode,MAY_WRITE)) != 0) (error = permission(inode,MAY_WRITE)) != 0)
goto dput_and_out; goto dput_and_out;
} }
down(&inode->i_sem);
error = notify_change(nd.dentry, &newattrs); error = notify_change(nd.dentry, &newattrs);
up(&inode->i_sem);
dput_and_out: dput_and_out:
path_release(&nd); path_release(&nd);
out: out:
...@@ -299,7 +305,9 @@ asmlinkage long sys_utimes(char * filename, struct timeval * utimes) ...@@ -299,7 +305,9 @@ asmlinkage long sys_utimes(char * filename, struct timeval * utimes)
if ((error = permission(inode,MAY_WRITE)) != 0) if ((error = permission(inode,MAY_WRITE)) != 0)
goto dput_and_out; goto dput_and_out;
} }
down(&inode->i_sem);
error = notify_change(nd.dentry, &newattrs); error = notify_change(nd.dentry, &newattrs);
up(&inode->i_sem);
dput_and_out: dput_and_out:
path_release(&nd); path_release(&nd);
out: out:
...@@ -449,11 +457,13 @@ asmlinkage long sys_fchmod(unsigned int fd, mode_t mode) ...@@ -449,11 +457,13 @@ asmlinkage long sys_fchmod(unsigned int fd, mode_t mode)
err = -EPERM; err = -EPERM;
if (IS_IMMUTABLE(inode) || IS_APPEND(inode)) if (IS_IMMUTABLE(inode) || IS_APPEND(inode))
goto out_putf; goto out_putf;
down(&inode->i_sem);
if (mode == (mode_t) -1) if (mode == (mode_t) -1)
mode = inode->i_mode; mode = inode->i_mode;
newattrs.ia_mode = (mode & S_IALLUGO) | (inode->i_mode & ~S_IALLUGO); newattrs.ia_mode = (mode & S_IALLUGO) | (inode->i_mode & ~S_IALLUGO);
newattrs.ia_valid = ATTR_MODE | ATTR_CTIME; newattrs.ia_valid = ATTR_MODE | ATTR_CTIME;
err = notify_change(dentry, &newattrs); err = notify_change(dentry, &newattrs);
up(&inode->i_sem);
out_putf: out_putf:
fput(file); fput(file);
...@@ -481,11 +491,13 @@ asmlinkage long sys_chmod(const char * filename, mode_t mode) ...@@ -481,11 +491,13 @@ asmlinkage long sys_chmod(const char * filename, mode_t mode)
if (IS_IMMUTABLE(inode) || IS_APPEND(inode)) if (IS_IMMUTABLE(inode) || IS_APPEND(inode))
goto dput_and_out; goto dput_and_out;
down(&inode->i_sem);
if (mode == (mode_t) -1) if (mode == (mode_t) -1)
mode = inode->i_mode; mode = inode->i_mode;
newattrs.ia_mode = (mode & S_IALLUGO) | (inode->i_mode & ~S_IALLUGO); newattrs.ia_mode = (mode & S_IALLUGO) | (inode->i_mode & ~S_IALLUGO);
newattrs.ia_valid = ATTR_MODE | ATTR_CTIME; newattrs.ia_valid = ATTR_MODE | ATTR_CTIME;
error = notify_change(nd.dentry, &newattrs); error = notify_change(nd.dentry, &newattrs);
up(&inode->i_sem);
dput_and_out: dput_and_out:
path_release(&nd); path_release(&nd);
...@@ -510,45 +522,20 @@ static int chown_common(struct dentry * dentry, uid_t user, gid_t group) ...@@ -510,45 +522,20 @@ static int chown_common(struct dentry * dentry, uid_t user, gid_t group)
error = -EPERM; error = -EPERM;
if (IS_IMMUTABLE(inode) || IS_APPEND(inode)) if (IS_IMMUTABLE(inode) || IS_APPEND(inode))
goto out; goto out;
if (user == (uid_t) -1) newattrs.ia_valid = ATTR_CTIME;
user = inode->i_uid; if (user != (uid_t) -1) {
if (group == (gid_t) -1) newattrs.ia_valid = ATTR_UID;
group = inode->i_gid; newattrs.ia_uid = user;
newattrs.ia_mode = inode->i_mode;
newattrs.ia_uid = user;
newattrs.ia_gid = group;
newattrs.ia_valid = ATTR_UID | ATTR_GID | ATTR_CTIME;
/*
* If the user or group of a non-directory has been changed by a
* non-root user, remove the setuid bit.
* 19981026 David C Niemi <niemi@tux.org>
*
* Changed this to apply to all users, including root, to avoid
* some races. This is the behavior we had in 2.0. The check for
* non-root was definitely wrong for 2.2 anyway, as it should
* have been using CAP_FSETID rather than fsuid -- 19990830 SD.
*/
if ((inode->i_mode & S_ISUID) == S_ISUID &&
!S_ISDIR(inode->i_mode))
{
newattrs.ia_mode &= ~S_ISUID;
newattrs.ia_valid |= ATTR_MODE;
} }
/* if (group != (gid_t) -1) {
* Likewise, if the user or group of a non-directory has been changed newattrs.ia_valid = ATTR_GID;
* by a non-root user, remove the setgid bit UNLESS there is no group newattrs.ia_gid = group;
* execute bit (this would be a file marked for mandatory locking).
* 19981026 David C Niemi <niemi@tux.org>
*
* Removed the fsuid check (see the comment above) -- 19990830 SD.
*/
if (((inode->i_mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP))
&& !S_ISDIR(inode->i_mode))
{
newattrs.ia_mode &= ~S_ISGID;
newattrs.ia_valid |= ATTR_MODE;
} }
if (!S_ISDIR(inode->i_mode))
newattrs.ia_valid |= ATTR_KILL_SUID|ATTR_KILL_SGID;
down(&inode->i_sem);
error = notify_change(dentry, &newattrs); error = notify_change(dentry, &newattrs);
up(&inode->i_sem);
out: out:
return error; return error;
} }
......
...@@ -305,6 +305,8 @@ extern void set_bh_page(struct buffer_head *bh, struct page *page, unsigned long ...@@ -305,6 +305,8 @@ extern void set_bh_page(struct buffer_head *bh, struct page *page, unsigned long
#define ATTR_MTIME_SET 256 #define ATTR_MTIME_SET 256
#define ATTR_FORCE 512 /* Not a change, but a change it */ #define ATTR_FORCE 512 /* Not a change, but a change it */
#define ATTR_ATTR_FLAG 1024 #define ATTR_ATTR_FLAG 1024
#define ATTR_KILL_SUID 2048
#define ATTR_KILL_SGID 4096
/* /*
* This is the Inode Attributes structure, used for notify_change(). It * This is the Inode Attributes structure, used for notify_change(). It
...@@ -1313,7 +1315,7 @@ static inline struct inode *iget(struct super_block *sb, unsigned long ino) ...@@ -1313,7 +1315,7 @@ static inline struct inode *iget(struct super_block *sb, unsigned long ino)
extern void clear_inode(struct inode *); extern void clear_inode(struct inode *);
extern struct inode *new_inode(struct super_block *); extern struct inode *new_inode(struct super_block *);
extern void remove_suid(struct inode *inode); extern void remove_suid(struct dentry *);
extern void insert_inode_hash(struct inode *); extern void insert_inode_hash(struct inode *);
extern void remove_inode_hash(struct inode *); extern void remove_inode_hash(struct inode *);
extern struct file * get_empty_filp(void); extern struct file * get_empty_filp(void);
......
...@@ -2503,18 +2503,19 @@ static inline struct page * __grab_cache_page(struct address_space *mapping, ...@@ -2503,18 +2503,19 @@ static inline struct page * __grab_cache_page(struct address_space *mapping,
return page; return page;
} }
inline void remove_suid(struct inode *inode) inline void remove_suid(struct dentry *dentry)
{ {
unsigned int mode; struct iattr newattrs;
struct inode *inode = dentry->d_inode;
unsigned int mode = inode->i_mode & (S_ISUID|S_ISGID|S_IXGRP);
/* set S_IGID if S_IXGRP is set, and always set S_ISUID */ if (!(mode & S_IXGRP))
mode = (inode->i_mode & S_IXGRP)*(S_ISGID/S_IXGRP) | S_ISUID; mode &= S_ISUID;
/* was any of the uid bits set? */ /* was any of the uid bits set? */
mode &= inode->i_mode;
if (mode && !capable(CAP_FSETID)) { if (mode && !capable(CAP_FSETID)) {
inode->i_mode &= ~mode; newattrs.ia_valid = ATTR_KILL_SUID | ATTR_KILL_SGID;
mark_inode_dirty(inode); notify_change(dentry, &newattrs);
} }
} }
...@@ -2646,7 +2647,7 @@ generic_file_write(struct file *file,const char *buf,size_t count, loff_t *ppos) ...@@ -2646,7 +2647,7 @@ generic_file_write(struct file *file,const char *buf,size_t count, loff_t *ppos)
if (count == 0) if (count == 0)
goto out; goto out;
remove_suid(inode); remove_suid(file->f_dentry);
inode->i_ctime = inode->i_mtime = CURRENT_TIME; inode->i_ctime = inode->i_mtime = CURRENT_TIME;
mark_inode_dirty_sync(inode); mark_inode_dirty_sync(inode);
......
...@@ -802,7 +802,7 @@ shmem_file_write(struct file *file,const char *buf,size_t count,loff_t *ppos) ...@@ -802,7 +802,7 @@ shmem_file_write(struct file *file,const char *buf,size_t count,loff_t *ppos)
status = 0; status = 0;
if (count) { if (count) {
remove_suid(inode); remove_suid(file->f_dentry);
inode->i_ctime = inode->i_mtime = CURRENT_TIME; inode->i_ctime = inode->i_mtime = CURRENT_TIME;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment