aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorOleg Nesterov <oleg@redhat.com>2014-01-11 13:19:32 -0500
committerAl Viro <viro@zeniv.linux.org.uk>2014-01-25 03:14:36 -0500
commita8d4b8345e0ee48b732126d980efaf0dc373e2b0 (patch)
tree4bdfc52ef4ad176f54e4ce7b81091c0fbef0fd04
parent2ccdc413196b43a02bb68b46be5b68850904e9ea (diff)
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>
-rw-r--r--fs/file.c4
-rw-r--r--include/linux/fdtable.h35
-rw-r--r--include/linux/rcupdate.h2
-rw-r--r--kernel/rcu/update.c11
4 files changed, 23 insertions, 29 deletions
diff --git a/fs/file.c b/fs/file.c
index 4a78f981557a..957cbc09b0db 100644
--- a/fs/file.c
+++ b/fs/file.c
@@ -707,7 +707,7 @@ struct file *fget_light(unsigned int fd, int *fput_needed)
707 707
708 *fput_needed = 0; 708 *fput_needed = 0;
709 if (atomic_read(&files->count) == 1) { 709 if (atomic_read(&files->count) == 1) {
710 file = fcheck_files(files, fd); 710 file = __fcheck_files(files, fd);
711 if (file && (file->f_mode & FMODE_PATH)) 711 if (file && (file->f_mode & FMODE_PATH))
712 file = NULL; 712 file = NULL;
713 } else { 713 } else {
@@ -735,7 +735,7 @@ struct file *fget_raw_light(unsigned int fd, int *fput_needed)
735 735
736 *fput_needed = 0; 736 *fput_needed = 0;
737 if (atomic_read(&files->count) == 1) { 737 if (atomic_read(&files->count) == 1) {
738 file = fcheck_files(files, fd); 738 file = __fcheck_files(files, fd);
739 } else { 739 } else {
740 rcu_read_lock(); 740 rcu_read_lock();
741 file = fcheck_files(files, fd); 741 file = fcheck_files(files, fd);
diff --git a/include/linux/fdtable.h b/include/linux/fdtable.h
index 085197bd8812..70e8e21c0a30 100644
--- a/include/linux/fdtable.h
+++ b/include/linux/fdtable.h
@@ -59,29 +59,36 @@ struct files_struct {
59 struct file __rcu * fd_array[NR_OPEN_DEFAULT]; 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
71struct file_operations; 62struct file_operations;
72struct vfsmount; 63struct vfsmount;
73struct dentry; 64struct dentry;
74 65
75extern void __init files_defer_init(void); 66extern void __init files_defer_init(void);
76 67
77static 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 */
77static inline struct file *__fcheck_files(struct files_struct *files, unsigned int fd)
78{ 78{
79 struct file * file = NULL; 79 struct fdtable *fdt = rcu_dereference_raw(files->fdt);
80 struct fdtable *fdt = files_fdtable(files);
81 80
82 if (fd < fdt->max_fds) 81 if (fd < fdt->max_fds)
83 file = rcu_dereference_check_fdtable(files, fdt->fd[fd]); 82 return rcu_dereference_raw(fdt->fd[fd]);
84 return file; 83 return NULL;
84}
85
86static 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/*
diff --git a/include/linux/rcupdate.h b/include/linux/rcupdate.h
index 39cbb889e20d..a2482cf90b6b 100644
--- a/include/linux/rcupdate.h
+++ b/include/linux/rcupdate.h
@@ -448,8 +448,6 @@ static inline int rcu_read_lock_sched_held(void)
448 448
449#ifdef CONFIG_PROVE_RCU 449#ifdef CONFIG_PROVE_RCU
450 450
451extern int rcu_my_thread_group_empty(void);
452
453/** 451/**
454 * rcu_lockdep_assert - emit lockdep splat if specified condition not met 452 * rcu_lockdep_assert - emit lockdep splat if specified condition not met
455 * @c: condition to check 453 * @c: condition to check
diff --git a/kernel/rcu/update.c b/kernel/rcu/update.c
index 6cb3dff89e2b..a3596c8ec9e4 100644
--- a/kernel/rcu/update.c
+++ b/kernel/rcu/update.c
@@ -195,17 +195,6 @@ void wait_rcu_gp(call_rcu_func_t crf)
195} 195}
196EXPORT_SYMBOL_GPL(wait_rcu_gp); 196EXPORT_SYMBOL_GPL(wait_rcu_gp);
197 197
198#ifdef CONFIG_PROVE_RCU
199/*
200 * wrapper function to avoid #include problems.
201 */
202int rcu_my_thread_group_empty(void)
203{
204 return thread_group_empty(current);
205}
206EXPORT_SYMBOL_GPL(rcu_my_thread_group_empty);
207#endif /* #ifdef CONFIG_PROVE_RCU */
208
209#ifdef CONFIG_DEBUG_OBJECTS_RCU_HEAD 198#ifdef CONFIG_DEBUG_OBJECTS_RCU_HEAD
210static inline void debug_init_rcu_head(struct rcu_head *head) 199static inline void debug_init_rcu_head(struct rcu_head *head)
211{ 200{