| From 781c2a5a5f75eacc04663aced0f0f1a648d4f308 Mon Sep 17 00:00:00 2001 |
| From: Jeff Layton <jlayton@redhat.com> |
| Date: Mon, 2 Dec 2013 15:26:19 -0500 |
| Subject: nfsd: when reusing an existing repcache entry, unhash it first |
| |
| From: Jeff Layton <jlayton@redhat.com> |
| |
| commit 781c2a5a5f75eacc04663aced0f0f1a648d4f308 upstream. |
| |
| The DRC code will attempt to reuse an existing, expired cache entry in |
| preference to allocating a new one. It'll then search the cache, and if |
| it gets a hit it'll then free the cache entry that it was going to |
| reuse. |
| |
| The cache code doesn't unhash the entry that it's going to reuse |
| however, so it's possible for it end up designating an entry for reuse |
| and then subsequently freeing the same entry after it finds it. This |
| leads it to a later use-after-free situation and usually some list |
| corruption warnings or an oops. |
| |
| Fix this by simply unhashing the entry that we intend to reuse. That |
| will mean that it's not findable via a search and should prevent this |
| situation from occurring. |
| |
| Reported-by: Christoph Hellwig <hch@infradead.org> |
| Reported-by: g. artim <gartim@gmail.com> |
| Signed-off-by: Jeff Layton <jlayton@redhat.com> |
| Signed-off-by: J. Bruce Fields <bfields@redhat.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| fs/nfsd/nfscache.c | 9 ++++++++- |
| 1 file changed, 8 insertions(+), 1 deletion(-) |
| |
| --- a/fs/nfsd/nfscache.c |
| +++ b/fs/nfsd/nfscache.c |
| @@ -132,6 +132,13 @@ nfsd_reply_cache_alloc(void) |
| } |
| |
| static void |
| +nfsd_reply_cache_unhash(struct svc_cacherep *rp) |
| +{ |
| + hlist_del_init(&rp->c_hash); |
| + list_del_init(&rp->c_lru); |
| +} |
| + |
| +static void |
| nfsd_reply_cache_free_locked(struct svc_cacherep *rp) |
| { |
| if (rp->c_type == RC_REPLBUFF && rp->c_replvec.iov_base) { |
| @@ -417,7 +424,7 @@ nfsd_cache_lookup(struct svc_rqst *rqstp |
| rp = list_first_entry(&lru_head, struct svc_cacherep, c_lru); |
| if (nfsd_cache_entry_expired(rp) || |
| num_drc_entries >= max_drc_entries) { |
| - lru_put_end(rp); |
| + nfsd_reply_cache_unhash(rp); |
| prune_cache_entries(); |
| goto search_cache; |
| } |