diff options
author | Theodore Ts'o <tytso@mit.edu> | 2014-03-24 14:43:12 -0400 |
---|---|---|
committer | Theodore Ts'o <tytso@mit.edu> | 2014-03-24 14:43:12 -0400 |
commit | 5f16f3225b06242a9ee876f07c1c9b6ed36a22b6 (patch) | |
tree | cebecf399c2d9ef5b0a24902638cb37d2e9e092b | |
parent | ed3654eb981fd44694b4d2a636e13f998bc10e7f (diff) |
ext4: atomically set inode->i_flags in ext4_set_inode_flags()
Use cmpxchg() to atomically set i_flags instead of clearing out the
S_IMMUTABLE, S_APPEND, etc. flags and then setting them from the
EXT4_IMMUTABLE_FL, EXT4_APPEND_FL flags, since this opens up a race
where an immutable file has the immutable flag cleared for a brief
window of time.
Reported-by: John Sullivan <jsrhbz@kanargh.force9.co.uk>
Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
Cc: stable@kernel.org
-rw-r--r-- | fs/ext4/inode.c | 14 | ||||
-rw-r--r-- | fs/inode.c | 31 | ||||
-rw-r--r-- | include/linux/fs.h | 3 |
3 files changed, 42 insertions, 6 deletions
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index b5e182acf9b9..df067c3c6c93 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c | |||
@@ -3938,18 +3938,20 @@ int ext4_get_inode_loc(struct inode *inode, struct ext4_iloc *iloc) | |||
3938 | void ext4_set_inode_flags(struct inode *inode) | 3938 | void ext4_set_inode_flags(struct inode *inode) |
3939 | { | 3939 | { |
3940 | unsigned int flags = EXT4_I(inode)->i_flags; | 3940 | unsigned int flags = EXT4_I(inode)->i_flags; |
3941 | unsigned int new_fl = 0; | ||
3941 | 3942 | ||
3942 | inode->i_flags &= ~(S_SYNC|S_APPEND|S_IMMUTABLE|S_NOATIME|S_DIRSYNC); | ||
3943 | if (flags & EXT4_SYNC_FL) | 3943 | if (flags & EXT4_SYNC_FL) |
3944 | inode->i_flags |= S_SYNC; | 3944 | new_fl |= S_SYNC; |
3945 | if (flags & EXT4_APPEND_FL) | 3945 | if (flags & EXT4_APPEND_FL) |
3946 | inode->i_flags |= S_APPEND; | 3946 | new_fl |= S_APPEND; |
3947 | if (flags & EXT4_IMMUTABLE_FL) | 3947 | if (flags & EXT4_IMMUTABLE_FL) |
3948 | inode->i_flags |= S_IMMUTABLE; | 3948 | new_fl |= S_IMMUTABLE; |
3949 | if (flags & EXT4_NOATIME_FL) | 3949 | if (flags & EXT4_NOATIME_FL) |
3950 | inode->i_flags |= S_NOATIME; | 3950 | new_fl |= S_NOATIME; |
3951 | if (flags & EXT4_DIRSYNC_FL) | 3951 | if (flags & EXT4_DIRSYNC_FL) |
3952 | inode->i_flags |= S_DIRSYNC; | 3952 | new_fl |= S_DIRSYNC; |
3953 | inode_set_flags(inode, new_fl, | ||
3954 | S_SYNC|S_APPEND|S_IMMUTABLE|S_NOATIME|S_DIRSYNC); | ||
3953 | } | 3955 | } |
3954 | 3956 | ||
3955 | /* Propagate flags from i_flags to EXT4_I(inode)->i_flags */ | 3957 | /* Propagate flags from i_flags to EXT4_I(inode)->i_flags */ |
diff --git a/fs/inode.c b/fs/inode.c index 4bcdad3c9361..26f95ceb6250 100644 --- a/fs/inode.c +++ b/fs/inode.c | |||
@@ -1899,3 +1899,34 @@ void inode_dio_done(struct inode *inode) | |||
1899 | wake_up_bit(&inode->i_state, __I_DIO_WAKEUP); | 1899 | wake_up_bit(&inode->i_state, __I_DIO_WAKEUP); |
1900 | } | 1900 | } |
1901 | EXPORT_SYMBOL(inode_dio_done); | 1901 | EXPORT_SYMBOL(inode_dio_done); |
1902 | |||
1903 | /* | ||
1904 | * inode_set_flags - atomically set some inode flags | ||
1905 | * | ||
1906 | * Note: the caller should be holding i_mutex, or else be sure that | ||
1907 | * they have exclusive access to the inode structure (i.e., while the | ||
1908 | * inode is being instantiated). The reason for the cmpxchg() loop | ||
1909 | * --- which wouldn't be necessary if all code paths which modify | ||
1910 | * i_flags actually followed this rule, is that there is at least one | ||
1911 | * code path which doesn't today --- for example, | ||
1912 | * __generic_file_aio_write() calls file_remove_suid() without holding | ||
1913 | * i_mutex --- so we use cmpxchg() out of an abundance of caution. | ||
1914 | * | ||
1915 | * In the long run, i_mutex is overkill, and we should probably look | ||
1916 | * at using the i_lock spinlock to protect i_flags, and then make sure | ||
1917 | * it is so documented in include/linux/fs.h and that all code follows | ||
1918 | * the locking convention!! | ||
1919 | */ | ||
1920 | void inode_set_flags(struct inode *inode, unsigned int flags, | ||
1921 | unsigned int mask) | ||
1922 | { | ||
1923 | unsigned int old_flags, new_flags; | ||
1924 | |||
1925 | WARN_ON_ONCE(flags & ~mask); | ||
1926 | do { | ||
1927 | old_flags = ACCESS_ONCE(inode->i_flags); | ||
1928 | new_flags = (old_flags & ~mask) | flags; | ||
1929 | } while (unlikely(cmpxchg(&inode->i_flags, old_flags, | ||
1930 | new_flags) != old_flags)); | ||
1931 | } | ||
1932 | EXPORT_SYMBOL(inode_set_flags); | ||
diff --git a/include/linux/fs.h b/include/linux/fs.h index 60829565e552..5d1f6fa8daed 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h | |||
@@ -2556,6 +2556,9 @@ static inline ssize_t blockdev_direct_IO(int rw, struct kiocb *iocb, | |||
2556 | void inode_dio_wait(struct inode *inode); | 2556 | void inode_dio_wait(struct inode *inode); |
2557 | void inode_dio_done(struct inode *inode); | 2557 | void inode_dio_done(struct inode *inode); |
2558 | 2558 | ||
2559 | extern void inode_set_flags(struct inode *inode, unsigned int flags, | ||
2560 | unsigned int mask); | ||
2561 | |||
2559 | extern const struct file_operations generic_ro_fops; | 2562 | extern const struct file_operations generic_ro_fops; |
2560 | 2563 | ||
2561 | #define special_file(m) (S_ISCHR(m)||S_ISBLK(m)||S_ISFIFO(m)||S_ISSOCK(m)) | 2564 | #define special_file(m) (S_ISCHR(m)||S_ISBLK(m)||S_ISFIFO(m)||S_ISSOCK(m)) |