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

introduce __fcheck_files() to fix rcu_dereference_check_fdtable(), kill rcu_my_thread_group_empty()

rcu_dereference_check_fdtable() looks very wrong,

1. rcu_my_thread_group_empty() was added by 844b9a8707f1 "vfs: fix
RCU-lockdep false positive due to /proc" but it doesn't really
fix the problem. A CLONE_THREAD (without CLONE_FILES) task can
hit the same race with get_files_struct().

And otoh rcu_my_thread_group_empty() can suppress the correct
warning if the caller is the CLONE_FILES (without CLONE_THREAD)
task.

2. files->count == 1 check is not really right too. Even if this
files_struct is not shared it is not safe to access it lockless
unless the caller is the owner.

Otoh, this check is sub-optimal. files->count == 0 always means
it is safe to use it lockless even if files != current->files,
but put_files_struct() has to take rcu_read_lock(). See the next
patch.

This patch removes the buggy checks and turns fcheck_files() into
__fcheck_files() which uses rcu_dereference_raw(), the "unshared"
callers, fget_light() and fget_raw_light(), can use it to avoid
the warning from RCU-lockdep.

fcheck_files() is trivially reimplemented as rcu_lockdep_assert()
plus __fcheck_files().

Signed-off-by: Oleg Nesterov <oleg@redhat.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

authored by

Oleg Nesterov and committed by
Al Viro
a8d4b834 2ccdc413

+23 -29
+2 -2
fs/file.c
··· 707 707 708 708 *fput_needed = 0; 709 709 if (atomic_read(&files->count) == 1) { 710 - file = fcheck_files(files, fd); 710 + file = __fcheck_files(files, fd); 711 711 if (file && (file->f_mode & FMODE_PATH)) 712 712 file = NULL; 713 713 } else { ··· 735 735 736 736 *fput_needed = 0; 737 737 if (atomic_read(&files->count) == 1) { 738 - file = fcheck_files(files, fd); 738 + file = __fcheck_files(files, fd); 739 739 } else { 740 740 rcu_read_lock(); 741 741 file = fcheck_files(files, fd);
+21 -14
include/linux/fdtable.h
··· 59 59 struct file __rcu * fd_array[NR_OPEN_DEFAULT]; 60 60 }; 61 61 62 - #define rcu_dereference_check_fdtable(files, fdtfd) \ 63 - (rcu_dereference_check((fdtfd), \ 64 - lockdep_is_held(&(files)->file_lock) || \ 65 - atomic_read(&(files)->count) == 1 || \ 66 - rcu_my_thread_group_empty())) 67 - 68 - #define files_fdtable(files) \ 69 - (rcu_dereference_check_fdtable((files), (files)->fdt)) 70 - 71 62 struct file_operations; 72 63 struct vfsmount; 73 64 struct dentry; 74 65 75 66 extern void __init files_defer_init(void); 76 67 77 - static inline struct file * fcheck_files(struct files_struct *files, unsigned int fd) 68 + #define rcu_dereference_check_fdtable(files, fdtfd) \ 69 + rcu_dereference_check((fdtfd), lockdep_is_held(&(files)->file_lock)) 70 + 71 + #define files_fdtable(files) \ 72 + rcu_dereference_check_fdtable((files), (files)->fdt) 73 + 74 + /* 75 + * The caller must ensure that fd table isn't shared or hold rcu or file lock 76 + */ 77 + static inline struct file *__fcheck_files(struct files_struct *files, unsigned int fd) 78 78 { 79 - struct file * file = NULL; 80 - struct fdtable *fdt = files_fdtable(files); 79 + struct fdtable *fdt = rcu_dereference_raw(files->fdt); 81 80 82 81 if (fd < fdt->max_fds) 83 - file = rcu_dereference_check_fdtable(files, fdt->fd[fd]); 84 - return file; 82 + return rcu_dereference_raw(fdt->fd[fd]); 83 + return NULL; 84 + } 85 + 86 + static inline struct file *fcheck_files(struct files_struct *files, unsigned int fd) 87 + { 88 + rcu_lockdep_assert(rcu_read_lock_held() || 89 + lockdep_is_held(&files->file_lock), 90 + "suspicious rcu_dereference_check() usage"); 91 + return __fcheck_files(files, fd); 85 92 } 86 93 87 94 /*
-2
include/linux/rcupdate.h
··· 448 448 449 449 #ifdef CONFIG_PROVE_RCU 450 450 451 - extern int rcu_my_thread_group_empty(void); 452 - 453 451 /** 454 452 * rcu_lockdep_assert - emit lockdep splat if specified condition not met 455 453 * @c: condition to check
-11
kernel/rcu/update.c
··· 195 195 } 196 196 EXPORT_SYMBOL_GPL(wait_rcu_gp); 197 197 198 - #ifdef CONFIG_PROVE_RCU 199 - /* 200 - * wrapper function to avoid #include problems. 201 - */ 202 - int rcu_my_thread_group_empty(void) 203 - { 204 - return thread_group_empty(current); 205 - } 206 - EXPORT_SYMBOL_GPL(rcu_my_thread_group_empty); 207 - #endif /* #ifdef CONFIG_PROVE_RCU */ 208 - 209 198 #ifdef CONFIG_DEBUG_OBJECTS_RCU_HEAD 210 199 static inline void debug_init_rcu_head(struct rcu_head *head) 211 200 {