From: René Scharfe Date: Thu, 15 Oct 2015 20:38:43 GMT Subject: Re: [PATCH v3] merge: fix cache_entry use-after-free Message-ID: <56200ED3.9000401@web.de> In-Reply-To: <1444935764.20887.1.camel@twopensource.com> Am 15.10.2015 um 21:02 schrieb David Turner: > On Thu, 2015-10-15 at 05:35 +0200, René Scharfe wrote: >> Am 15.10.2015 um 00:07 schrieb David Turner: >>> From: Keith McGuigan >>> >>> During merges, we would previously free entries that we no longer need >>> in the destination index. But those entries might also be stored in >>> the dir_entry cache, and when a later call to add_to_index found them, >>> they would be used after being freed. >>> >>> To prevent this, add a ref count for struct cache_entry. Whenever >>> a cache entry is added to a data structure, the ref count is incremented; >>> when it is removed from the data structure, it is decremented. When >>> it hits zero, the cache_entry is freed. >>> >>> Signed-off-by: Keith McGuigan >>> Signed-off-by: David Turner >>> --- >>> >>> Fix type of ref_count (from unsigned int to int). >>> >>> >>> cache.h | 27 +++++++++++++++++++++++++++ >>> name-hash.c | 7 ++++++- >>> read-cache.c | 6 +++++- >>> split-index.c | 13 ++++++++----- >>> unpack-trees.c | 6 ++++-- >>> 5 files changed, 50 insertions(+), 9 deletions(-) >>> >>> diff --git a/cache.h b/cache.h >>> index 752031e..7906026 100644 >>> --- a/cache.h >>> +++ b/cache.h >>> @@ -149,6 +149,7 @@ struct stat_data { >>> >>> struct cache_entry { >>> struct hashmap_entry ent; >>> + int ref_count; /* count the number of refs to this in dir_hash */ >>> struct stat_data ce_stat_data; >>> unsigned int ce_mode; >>> unsigned int ce_flags; >>> @@ -213,6 +214,32 @@ struct cache_entry { >>> struct pathspec; >>> >>> /* >>> + * Increment the cache_entry reference count. Should be called >>> + * whenever a pointer to a cache_entry is retained in a data structure, >>> + * thus marking it as alive. >>> + */ >>> +static inline void add_ce_ref(struct cache_entry *ce) >>> +{ >>> + assert(ce != NULL && ce->ref_count >= 0); >>> + ce->ref_count++; >>> +} >>> + >>> +/* >>> + * Decrement the cache_entry reference count. Should be called whenever >>> + * a pointer to a cache_entry is dropped. Once the counter drops to 0 >>> + * the cache_entry memory will be safely freed. >>> + */ >>> +static inline void drop_ce_ref(struct cache_entry *ce) >>> +{ >>> + if (ce != NULL) { >>> + assert(ce->ref_count >= 0); >> >> Shouldn't this be "> 0" to prevent double frees? > > No. If the ref_count is 1, then there is still some reference to the > ce. If it is 0, there is no reference to it, and the next check (< 1) > will succeed and the ce will get freed. > >>> + if (--ce->ref_count < 1) { >>> + free(ce); >>> + } >>> + } >>> +} OK, let me think out loud, step by step: Given ref_count == 1 then the assert passes, ref_count gets decremented to 0, which is less than 1, so ce is freed. Given ref_count == 0 then the assert passes, refcount gets decremented to -1, which is less than 1, so ce is freed again. Where did I go wrong? René