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
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

authored by Theodore Ts'o and committed by Linus Torvalds 00a1a053 981e893e

Changed files
+24 -6
fs
ext4
include
linux
+9 -6
fs/ext4/inode.c
··· 38 38 #include <linux/slab.h> 39 39 #include <linux/ratelimit.h> 40 40 #include <linux/aio.h> 41 + #include <linux/bitops.h> 41 42 42 43 #include "ext4_jbd2.h" 43 44 #include "xattr.h" ··· 3922 3921 void ext4_set_inode_flags(struct inode *inode) 3923 3922 { 3924 3923 unsigned int flags = EXT4_I(inode)->i_flags; 3924 + unsigned int new_fl = 0; 3925 3925 3926 - inode->i_flags &= ~(S_SYNC|S_APPEND|S_IMMUTABLE|S_NOATIME|S_DIRSYNC); 3927 3926 if (flags & EXT4_SYNC_FL) 3928 - inode->i_flags |= S_SYNC; 3927 + new_fl |= S_SYNC; 3929 3928 if (flags & EXT4_APPEND_FL) 3930 - inode->i_flags |= S_APPEND; 3929 + new_fl |= S_APPEND; 3931 3930 if (flags & EXT4_IMMUTABLE_FL) 3932 - inode->i_flags |= S_IMMUTABLE; 3931 + new_fl |= S_IMMUTABLE; 3933 3932 if (flags & EXT4_NOATIME_FL) 3934 - inode->i_flags |= S_NOATIME; 3933 + new_fl |= S_NOATIME; 3935 3934 if (flags & EXT4_DIRSYNC_FL) 3936 - inode->i_flags |= S_DIRSYNC; 3935 + new_fl |= S_DIRSYNC; 3936 + set_mask_bits(&inode->i_flags, 3937 + S_SYNC|S_APPEND|S_IMMUTABLE|S_NOATIME|S_DIRSYNC, new_fl); 3937 3938 } 3938 3939 3939 3940 /* Propagate flags from i_flags to EXT4_I(inode)->i_flags */
+15
include/linux/bitops.h
··· 196 196 197 197 #ifdef __KERNEL__ 198 198 199 + #ifndef set_mask_bits 200 + #define set_mask_bits(ptr, _mask, _bits) \ 201 + ({ \ 202 + const typeof(*ptr) mask = (_mask), bits = (_bits); \ 203 + typeof(*ptr) old, new; \ 204 + \ 205 + do { \ 206 + old = ACCESS_ONCE(*ptr); \ 207 + new = (old & ~mask) | bits; \ 208 + } while (cmpxchg(ptr, old, new) != old); \ 209 + \ 210 + new; \ 211 + }) 212 + #endif 213 + 199 214 #ifndef find_last_bit 200 215 /** 201 216 * find_last_bit - find the last set bit in a memory region