| From 17ad31b3a43b72aec3a3d83605891e1397d0d065 Mon Sep 17 00:00:00 2001 |
| From: Jeff Layton <jlayton@kernel.org> |
| Date: Mon, 23 Feb 2026 12:09:58 -0500 |
| Subject: sunrpc: fix cache_request leak in cache_release |
| |
| From: Jeff Layton <jlayton@kernel.org> |
| |
| commit 17ad31b3a43b72aec3a3d83605891e1397d0d065 upstream. |
| |
| When a reader's file descriptor is closed while in the middle of reading |
| a cache_request (rp->offset != 0), cache_release() decrements the |
| request's readers count but never checks whether it should free the |
| request. |
| |
| In cache_read(), when readers drops to 0 and CACHE_PENDING is clear, the |
| cache_request is removed from the queue and freed along with its buffer |
| and cache_head reference. cache_release() lacks this cleanup. |
| |
| The only other path that frees requests with readers == 0 is |
| cache_dequeue(), but it runs only when CACHE_PENDING transitions from |
| set to clear. If that transition already happened while readers was |
| still non-zero, cache_dequeue() will have skipped the request, and no |
| subsequent call will clean it up. |
| |
| Add the same cleanup logic from cache_read() to cache_release(): after |
| decrementing readers, check if it reached 0 with CACHE_PENDING clear, |
| and if so, dequeue and free the cache_request. |
| |
| Reported-by: NeilBrown <neilb@ownmail.net> |
| Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") |
| Cc: stable@kernel.org |
| Signed-off-by: Jeff Layton <jlayton@kernel.org> |
| Signed-off-by: Chuck Lever <chuck.lever@oracle.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| net/sunrpc/cache.c | 26 +++++++++++++++++++++----- |
| 1 file changed, 21 insertions(+), 5 deletions(-) |
| |
| --- a/net/sunrpc/cache.c |
| +++ b/net/sunrpc/cache.c |
| @@ -1056,14 +1056,25 @@ static int cache_release(struct inode *i |
| struct cache_reader *rp = filp->private_data; |
| |
| if (rp) { |
| + struct cache_request *rq = NULL; |
| + |
| spin_lock(&queue_lock); |
| if (rp->offset) { |
| struct cache_queue *cq; |
| - for (cq= &rp->q; &cq->list != &cd->queue; |
| - cq = list_entry(cq->list.next, struct cache_queue, list)) |
| + for (cq = &rp->q; &cq->list != &cd->queue; |
| + cq = list_entry(cq->list.next, |
| + struct cache_queue, list)) |
| if (!cq->reader) { |
| - container_of(cq, struct cache_request, q) |
| - ->readers--; |
| + struct cache_request *cr = |
| + container_of(cq, |
| + struct cache_request, q); |
| + cr->readers--; |
| + if (cr->readers == 0 && |
| + !test_bit(CACHE_PENDING, |
| + &cr->item->flags)) { |
| + list_del(&cr->q.list); |
| + rq = cr; |
| + } |
| break; |
| } |
| rp->offset = 0; |
| @@ -1071,9 +1082,14 @@ static int cache_release(struct inode *i |
| list_del(&rp->q.list); |
| spin_unlock(&queue_lock); |
| |
| + if (rq) { |
| + cache_put(rq->item, cd); |
| + kfree(rq->buf); |
| + kfree(rq); |
| + } |
| + |
| filp->private_data = NULL; |
| kfree(rp); |
| - |
| } |
| if (filp->f_mode & FMODE_WRITE) { |
| atomic_dec(&cd->writers); |