diff options
Diffstat (limited to 'kernel/watch_queue.c')
| -rw-r--r-- | kernel/watch_queue.c | 103 | 
1 files changed, 69 insertions, 34 deletions
| diff --git a/kernel/watch_queue.c b/kernel/watch_queue.c index 230038d4f908..59ddb00d6944 100644 --- a/kernel/watch_queue.c +++ b/kernel/watch_queue.c @@ -34,6 +34,27 @@ MODULE_LICENSE("GPL");  #define WATCH_QUEUE_NOTE_SIZE 128  #define WATCH_QUEUE_NOTES_PER_PAGE (PAGE_SIZE / WATCH_QUEUE_NOTE_SIZE) +/* + * This must be called under the RCU read-lock, which makes + * sure that the wqueue still exists. It can then take the lock, + * and check that the wqueue hasn't been destroyed, which in + * turn makes sure that the notification pipe still exists. + */ +static inline bool lock_wqueue(struct watch_queue *wqueue) +{ +	spin_lock_bh(&wqueue->lock); +	if (unlikely(wqueue->defunct)) { +		spin_unlock_bh(&wqueue->lock); +		return false; +	} +	return true; +} + +static inline void unlock_wqueue(struct watch_queue *wqueue) +{ +	spin_unlock_bh(&wqueue->lock); +} +  static void watch_queue_pipe_buf_release(struct pipe_inode_info *pipe,  					 struct pipe_buffer *buf)  { @@ -69,6 +90,10 @@ static const struct pipe_buf_operations watch_queue_pipe_buf_ops = {  /*   * Post a notification to a watch queue. + * + * Must be called with the RCU lock for reading, and the + * watch_queue lock held, which guarantees that the pipe + * hasn't been released.   */  static bool post_one_notification(struct watch_queue *wqueue,  				  struct watch_notification *n) @@ -85,9 +110,6 @@ static bool post_one_notification(struct watch_queue *wqueue,  	spin_lock_irq(&pipe->rd_wait.lock); -	if (wqueue->defunct) -		goto out; -  	mask = pipe->ring_size - 1;  	head = pipe->head;  	tail = pipe->tail; @@ -203,7 +225,10 @@ void __post_watch_notification(struct watch_list *wlist,  		if (security_post_notification(watch->cred, cred, n) < 0)  			continue; -		post_one_notification(wqueue, n); +		if (lock_wqueue(wqueue)) { +			post_one_notification(wqueue, n); +			unlock_wqueue(wqueue); +		}  	}  	rcu_read_unlock(); @@ -429,6 +454,33 @@ void init_watch(struct watch *watch, struct watch_queue *wqueue)  	rcu_assign_pointer(watch->queue, wqueue);  } +static int add_one_watch(struct watch *watch, struct watch_list *wlist, struct watch_queue *wqueue) +{ +	const struct cred *cred; +	struct watch *w; + +	hlist_for_each_entry(w, &wlist->watchers, list_node) { +		struct watch_queue *wq = rcu_access_pointer(w->queue); +		if (wqueue == wq && watch->id == w->id) +			return -EBUSY; +	} + +	cred = current_cred(); +	if (atomic_inc_return(&cred->user->nr_watches) > task_rlimit(current, RLIMIT_NOFILE)) { +		atomic_dec(&cred->user->nr_watches); +		return -EAGAIN; +	} + +	watch->cred = get_cred(cred); +	rcu_assign_pointer(watch->watch_list, wlist); + +	kref_get(&wqueue->usage); +	kref_get(&watch->usage); +	hlist_add_head(&watch->queue_node, &wqueue->watches); +	hlist_add_head_rcu(&watch->list_node, &wlist->watchers); +	return 0; +} +  /**   * add_watch_to_object - Add a watch on an object to a watch list   * @watch: The watch to add @@ -443,33 +495,21 @@ void init_watch(struct watch *watch, struct watch_queue *wqueue)   */  int add_watch_to_object(struct watch *watch, struct watch_list *wlist)  { -	struct watch_queue *wqueue = rcu_access_pointer(watch->queue); -	struct watch *w; - -	hlist_for_each_entry(w, &wlist->watchers, list_node) { -		struct watch_queue *wq = rcu_access_pointer(w->queue); -		if (wqueue == wq && watch->id == w->id) -			return -EBUSY; -	} +	struct watch_queue *wqueue; +	int ret = -ENOENT; -	watch->cred = get_current_cred(); -	rcu_assign_pointer(watch->watch_list, wlist); +	rcu_read_lock(); -	if (atomic_inc_return(&watch->cred->user->nr_watches) > -	    task_rlimit(current, RLIMIT_NOFILE)) { -		atomic_dec(&watch->cred->user->nr_watches); -		put_cred(watch->cred); -		return -EAGAIN; +	wqueue = rcu_access_pointer(watch->queue); +	if (lock_wqueue(wqueue)) { +		spin_lock(&wlist->lock); +		ret = add_one_watch(watch, wlist, wqueue); +		spin_unlock(&wlist->lock); +		unlock_wqueue(wqueue);  	} -	spin_lock_bh(&wqueue->lock); -	kref_get(&wqueue->usage); -	kref_get(&watch->usage); -	hlist_add_head(&watch->queue_node, &wqueue->watches); -	spin_unlock_bh(&wqueue->lock); - -	hlist_add_head(&watch->list_node, &wlist->watchers); -	return 0; +	rcu_read_unlock(); +	return ret;  }  EXPORT_SYMBOL(add_watch_to_object); @@ -520,20 +560,15 @@ found:  	wqueue = rcu_dereference(watch->queue); -	/* We don't need the watch list lock for the next bit as RCU is -	 * protecting *wqueue from deallocation. -	 */ -	if (wqueue) { +	if (lock_wqueue(wqueue)) {  		post_one_notification(wqueue, &n.watch); -		spin_lock_bh(&wqueue->lock); -  		if (!hlist_unhashed(&watch->queue_node)) {  			hlist_del_init_rcu(&watch->queue_node);  			put_watch(watch);  		} -		spin_unlock_bh(&wqueue->lock); +		unlock_wqueue(wqueue);  	}  	if (wlist->release_watch) { | 
