Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

fsnotify: Avoid data race between fsnotify_recalc_mask() and fsnotify_object_watched()

When __fsnotify_recalc_mask() recomputes the mask on the watched object,
the compiler can "optimize" the code to perform partial updates to the
mask (including zeroing it at the beginning). Thus places checking
the object mask without conn->lock such as fsnotify_object_watched()
could see invalid states of the mask. Make sure the mask update is
performed by one memory store using WRITE_ONCE().

Reported-by: syzbot+701037856c25b143f1ad@syzkaller.appspotmail.com
Reported-by: Dmitry Vyukov <dvyukov@google.com>
Link: https://lore.kernel.org/all/CACT4Y+Zk0ohwwwHSD63U2-PQ=UuamXczr1mKBD6xtj2dyYKBvA@mail.gmail.com
Signed-off-by: Jan Kara <jack@suse.cz>
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Link: https://patch.msgid.link/20240717140623.27768-1-jack@suse.cz

Jan Kara 35ceae44 9852d85e

+19 -12
+12 -9
fs/notify/fsnotify.c
··· 183 183 BUILD_BUG_ON(FS_EVENTS_POSS_ON_CHILD & ~FS_EVENTS_POSS_TO_PARENT); 184 184 185 185 /* Did either inode/sb/mount subscribe for events with parent/name? */ 186 - marks_mask |= fsnotify_parent_needed_mask(inode->i_fsnotify_mask); 187 - marks_mask |= fsnotify_parent_needed_mask(inode->i_sb->s_fsnotify_mask); 186 + marks_mask |= fsnotify_parent_needed_mask( 187 + READ_ONCE(inode->i_fsnotify_mask)); 188 + marks_mask |= fsnotify_parent_needed_mask( 189 + READ_ONCE(inode->i_sb->s_fsnotify_mask)); 188 190 marks_mask |= fsnotify_parent_needed_mask(mnt_mask); 189 191 190 192 /* Did they subscribe for this event with parent/name info? */ ··· 197 195 static inline bool fsnotify_object_watched(struct inode *inode, __u32 mnt_mask, 198 196 __u32 mask) 199 197 { 200 - __u32 marks_mask = inode->i_fsnotify_mask | mnt_mask | 201 - inode->i_sb->s_fsnotify_mask; 198 + __u32 marks_mask = READ_ONCE(inode->i_fsnotify_mask) | mnt_mask | 199 + READ_ONCE(inode->i_sb->s_fsnotify_mask); 202 200 203 201 return mask & marks_mask & ALL_FSNOTIFY_EVENTS; 204 202 } ··· 215 213 int data_type) 216 214 { 217 215 const struct path *path = fsnotify_data_path(data, data_type); 218 - __u32 mnt_mask = path ? real_mount(path->mnt)->mnt_fsnotify_mask : 0; 216 + __u32 mnt_mask = path ? 217 + READ_ONCE(real_mount(path->mnt)->mnt_fsnotify_mask) : 0; 219 218 struct inode *inode = d_inode(dentry); 220 219 struct dentry *parent; 221 220 bool parent_watched = dentry->d_flags & DCACHE_FSNOTIFY_PARENT_WATCHED; ··· 560 557 (!inode2 || !inode2->i_fsnotify_marks)) 561 558 return 0; 562 559 563 - marks_mask = sb->s_fsnotify_mask; 560 + marks_mask = READ_ONCE(sb->s_fsnotify_mask); 564 561 if (mnt) 565 - marks_mask |= mnt->mnt_fsnotify_mask; 562 + marks_mask |= READ_ONCE(mnt->mnt_fsnotify_mask); 566 563 if (inode) 567 - marks_mask |= inode->i_fsnotify_mask; 564 + marks_mask |= READ_ONCE(inode->i_fsnotify_mask); 568 565 if (inode2) 569 - marks_mask |= inode2->i_fsnotify_mask; 566 + marks_mask |= READ_ONCE(inode2->i_fsnotify_mask); 570 567 571 568 572 569 /*
+1 -1
fs/notify/inotify/inotify_user.c
··· 569 569 /* more bits in old than in new? */ 570 570 int dropped = (old_mask & ~new_mask); 571 571 /* more bits in this fsn_mark than the inode's mask? */ 572 - int do_inode = (new_mask & ~inode->i_fsnotify_mask); 572 + int do_inode = (new_mask & ~READ_ONCE(inode->i_fsnotify_mask)); 573 573 574 574 /* update the inode with this new fsn_mark */ 575 575 if (dropped || do_inode)
+6 -2
fs/notify/mark.c
··· 128 128 if (WARN_ON(!fsnotify_valid_obj_type(conn->type))) 129 129 return 0; 130 130 131 - return *fsnotify_conn_mask_p(conn); 131 + return READ_ONCE(*fsnotify_conn_mask_p(conn)); 132 132 } 133 133 134 134 static void fsnotify_get_sb_watched_objects(struct super_block *sb) ··· 245 245 !(mark->flags & FSNOTIFY_MARK_FLAG_NO_IREF)) 246 246 want_iref = true; 247 247 } 248 - *fsnotify_conn_mask_p(conn) = new_mask; 248 + /* 249 + * We use WRITE_ONCE() to prevent silly compiler optimizations from 250 + * confusing readers not holding conn->lock with partial updates. 251 + */ 252 + WRITE_ONCE(*fsnotify_conn_mask_p(conn), new_mask); 249 253 250 254 return fsnotify_update_iref(conn, want_iref); 251 255 }