Commit fb6e71db authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull nfsd fix from Chuck Lever:

 - Nail another UAF in NFSD's filecache

* tag 'nfsd-6.2-5' of git://git.kernel.org/pub/scm/linux/kernel/git/cel/linux:
  nfsd: don't free files unconditionally in __nfsd_file_cache_purge
parents 50306df3 4bdbba54
Loading
Loading
Loading
Loading
+36 −25
Original line number Diff line number Diff line
@@ -661,6 +661,39 @@ static struct shrinker nfsd_file_shrinker = {
	.seeks = 1,
};

/**
 * nfsd_file_cond_queue - conditionally unhash and queue a nfsd_file
 * @nf: nfsd_file to attempt to queue
 * @dispose: private list to queue successfully-put objects
 *
 * Unhash an nfsd_file, try to get a reference to it, and then put that
 * reference. If it's the last reference, queue it to the dispose list.
 */
static void
nfsd_file_cond_queue(struct nfsd_file *nf, struct list_head *dispose)
	__must_hold(RCU)
{
	int decrement = 1;

	/* If we raced with someone else unhashing, ignore it */
	if (!nfsd_file_unhash(nf))
		return;

	/* If we can't get a reference, ignore it */
	if (!nfsd_file_get(nf))
		return;

	/* Extra decrement if we remove from the LRU */
	if (nfsd_file_lru_remove(nf))
		++decrement;

	/* If refcount goes to 0, then put on the dispose list */
	if (refcount_sub_and_test(decrement, &nf->nf_ref)) {
		list_add(&nf->nf_lru, dispose);
		trace_nfsd_file_closing(nf);
	}
}

/**
 * nfsd_file_queue_for_close: try to close out any open nfsd_files for an inode
 * @inode:   inode on which to close out nfsd_files
@@ -688,30 +721,11 @@ nfsd_file_queue_for_close(struct inode *inode, struct list_head *dispose)

	rcu_read_lock();
	do {
		int decrement = 1;

		nf = rhashtable_lookup(&nfsd_file_rhash_tbl, &key,
				       nfsd_file_rhash_params);
		if (!nf)
			break;

		/* If we raced with someone else unhashing, ignore it */
		if (!nfsd_file_unhash(nf))
			continue;

		/* If we can't get a reference, ignore it */
		if (!nfsd_file_get(nf))
			continue;

		/* Extra decrement if we remove from the LRU */
		if (nfsd_file_lru_remove(nf))
			++decrement;

		/* If refcount goes to 0, then put on the dispose list */
		if (refcount_sub_and_test(decrement, &nf->nf_ref)) {
			list_add(&nf->nf_lru, dispose);
			trace_nfsd_file_closing(nf);
		}
		nfsd_file_cond_queue(nf, dispose);
	} while (1);
	rcu_read_unlock();
}
@@ -928,11 +942,8 @@ __nfsd_file_cache_purge(struct net *net)

		nf = rhashtable_walk_next(&iter);
		while (!IS_ERR_OR_NULL(nf)) {
			if (!net || nf->nf_net == net) {
				nfsd_file_unhash(nf);
				nfsd_file_lru_remove(nf);
				list_add(&nf->nf_lru, &dispose);
			}
			if (!net || nf->nf_net == net)
				nfsd_file_cond_queue(nf, &dispose);
			nf = rhashtable_walk_next(&iter);
		}