watch_queue: prevent dangling pipe pointer
authorSiddh Raman Pant <code@siddh.me>
Mon, 5 Jun 2023 14:36:16 +0000 (20:06 +0530)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 19 Jul 2023 14:22:10 +0000 (16:22 +0200)
commit 943211c87427f25bd22e0e63849fb486bb5f87fa upstream.

NULL the dangling pipe reference while clearing watch_queue.

If not done, a reference to a freed pipe remains in the watch_queue,
as this function is called before freeing a pipe in free_pipe_info()
(see line 834 of fs/pipe.c).

The sole use of wqueue->defunct is for checking if the watch queue has
been cleared, but wqueue->pipe is also NULLed while clearing.

Thus, wqueue->defunct is superfluous, as wqueue->pipe can be checked
for NULL. Hence, the former can be removed.

Tested with keyutils testsuite.

Cc: stable@vger.kernel.org # 6.1
Signed-off-by: Siddh Raman Pant <code@siddh.me>
Acked-by: David Howells <dhowells@redhat.com>
Message-Id: <20230605143616.640517-1-code@siddh.me>
Signed-off-by: Christian Brauner <brauner@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
include/linux/watch_queue.h
kernel/watch_queue.c

index fc6bba2..45cd42f 100644 (file)
@@ -38,7 +38,7 @@ struct watch_filter {
 struct watch_queue {
        struct rcu_head         rcu;
        struct watch_filter __rcu *filter;
-       struct pipe_inode_info  *pipe;          /* The pipe we're using as a buffer */
+       struct pipe_inode_info  *pipe;          /* Pipe we use as a buffer, NULL if queue closed */
        struct hlist_head       watches;        /* Contributory watches */
        struct page             **notes;        /* Preallocated notifications */
        unsigned long           *notes_bitmap;  /* Allocation bitmap for notes */
@@ -46,7 +46,6 @@ struct watch_queue {
        spinlock_t              lock;
        unsigned int            nr_notes;       /* Number of notes */
        unsigned int            nr_pages;       /* Number of pages in notes[] */
-       bool                    defunct;        /* T when queues closed */
 };
 
 /*
index f10f403..28ed71d 100644 (file)
@@ -43,7 +43,7 @@ MODULE_LICENSE("GPL");
 static inline bool lock_wqueue(struct watch_queue *wqueue)
 {
        spin_lock_bh(&wqueue->lock);
-       if (unlikely(wqueue->defunct)) {
+       if (unlikely(!wqueue->pipe)) {
                spin_unlock_bh(&wqueue->lock);
                return false;
        }
@@ -105,9 +105,6 @@ static bool post_one_notification(struct watch_queue *wqueue,
        unsigned int head, tail, mask, note, offset, len;
        bool done = false;
 
-       if (!pipe)
-               return false;
-
        spin_lock_irq(&pipe->rd_wait.lock);
 
        mask = pipe->ring_size - 1;
@@ -604,8 +601,11 @@ void watch_queue_clear(struct watch_queue *wqueue)
        rcu_read_lock();
        spin_lock_bh(&wqueue->lock);
 
-       /* Prevent new notifications from being stored. */
-       wqueue->defunct = true;
+       /*
+        * This pipe can be freed by callers like free_pipe_info().
+        * Removing this reference also prevents new notifications.
+        */
+       wqueue->pipe = NULL;
 
        while (!hlist_empty(&wqueue->watches)) {
                watch = hlist_entry(wqueue->watches.first, struct watch, queue_node);