Commit 17ad31b3 authored by Jeff Layton's avatar Jeff Layton Committed by Chuck Lever
Browse files

sunrpc: fix cache_request leak in cache_release



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: default avatarNeilBrown <neilb@ownmail.net>
Fixes: 1da177e4 ("Linux-2.6.12-rc2")
Cc: stable@kernel.org
Signed-off-by: default avatarJeff Layton <jlayton@kernel.org>
Signed-off-by: default avatarChuck Lever <chuck.lever@oracle.com>
parent e7fcf179
Loading
Loading
Loading
Loading
+21 −5
Original line number Diff line number Diff line
@@ -1061,14 +1061,25 @@ static int cache_release(struct inode *inode, struct file *filp,
	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))
			     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;
@@ -1076,9 +1087,14 @@ static int cache_release(struct inode *inode, struct file *filp,
		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);