Hi!

I want to report two bugs in Subversion 1.8.5 libsvn_subr subversion/libsvn_subr/cache-membuffer.c, a more important one and a less important one. I've fixed them, and patches are attached - please apply them.

First bug (important):

There is an incorrect integer addition to cache->data_used which makes 32-bit threaded svnserve sometimes hang and eat 100% CPU. When it hangs, one thread is doing an infinite loop in ensure_data_insertable() with write lock acquired, and others just wait for it to release the lock. So it looks like 100% usage of a single CPU core and non-working svn:// access.

The problem is in line 1377: "cache->data_used += size - entry->size;" is incorrect because cache->data_used is always apr_uint64_t, and size and entry->size are size_t which is unsigned 32-bit integer type on 32-bit platforms. And size is always [almost always] less than entry->size at that line, so cache->data_used becomes > 0x100000000 at once which makes ensure_data_insertable() to loop infinitely because all entries in cache "become" less than 1/8 * average entry size.

The patch is in the attachment 'fix-membuffer-u32-overflow'

Second bug (less important):

There is also another bug in libsvn_subr membuffer cache implementation: cache->hit_count and entry->hit_count are incremented without proper mutual exclusion (only with read lock acquired), so some increments get lost in multithreaded svnserve; as it's unlikely two threads access the same entry at once, usually cache->hit_count (not entry->hit_count) increments are lost, which leads to cache->hit_count being less than sum(entry->hit_count).

So cache->hit_count may overflow during subtraction of entry->hit_count from it and lead to removing of extra values from the cache in ensure_data_insertable(), because the retain threshold may be very big; also it may overflow again, become less than cache->entries_used and also lead to removing of extra items.

Instead of killing the performance by mutual exclusion I think it's possible to just check for integer overflow.

This doesn't affect general svnserve usability, so severity is minor.

The patch is in the attachment 'fix-membuffer-hit_count-overflow'

Hope you'll apply these patches soon, or fix the bugs some another way :)

--
With best regards,
  Vitaliy Filippov
See the comment.

--- a/subversion/libsvn_subr/cache-membuffer.c  2014-02-12 21:53:58.547230675 
+0000
+++ a/subversion/libsvn_subr/cache-membuffer.c  2014-02-12 22:02:11.039246322 
+0000
@@ -639,9 +639,16 @@ drop_entry(svn_membuffer_t *cache, entry
   assert(idx <= last_in_group);
 
   /* update global cache usage counters
+   *
+   * cache->hit_count and entry->hit_count are incremented without proper 
mutual
+   * exclusion (only with read lock), so some increments get lost in 
multithreaded svnserve;
+   * as it's unlikely two threads access the same entry at once usually it 
leads to
+   * cache->hit_count being less than sum(entry->hit_count). So it may overflow
+   * during subtraction and lead to emptying the cache in 
ensure_data_insertable().
+   * instead of killing the performance by mutual exclusion we just check for 
possible overflow.
    */
   cache->used_entries--;
-  cache->hit_count -= entry->hit_count;
+  cache->hit_count = cache->hit_count < entry->hit_count ? 0 : 
cache->hit_count-entry->hit_count;
   cache->data_used -= entry->size;
 
   /* extend the insertion window, if the entry happens to border it
@@ -802,7 +809,7 @@ let_entry_age(svn_membuffer_t *cache, en
 {
   apr_uint32_t hits_removed = (entry->hit_count + 1) >> 1;
 
-  cache->hit_count -= hits_removed;
+  cache->hit_count = cache->hit_count < hits_removed ? 0 : 
cache->hit_count-hits_removed;
   entry->hit_count -= hits_removed;
 }
 
This patch fixes the bug which makes 32-bit svnserve hang after some period of 
time
doing an infinite loop inside ensure_data_insertable() because cache->data_used 
becomes
a very big value, and ensure_data_insertable() never removes entries smaller 
than
cache->data_used / cache->used_entries / 8.

--- a/subversion/libsvn_subr/cache-membuffer.c  2014-02-12 21:42:12.483208244 
+0000
+++ b/subversion/libsvn_subr/cache-membuffer.c  2014-02-12 21:45:54.275215290 
+0000
@@ -1374,7 +1374,9 @@ membuffer_cache_set_internal(svn_membuff
    * the old spot, just re-use that space. */
   if (entry && ALIGN_VALUE(entry->size) >= size && buffer)
     {
-      cache->data_used += size - entry->size;
+      /* not "+=" because (size - entry_size) is almost always a big 32-bit
+         unsigned representation of a negative value on 32-bit platforms */
+      cache->data_used = cache->data_used + size - entry->size;
       entry->size = size;
 
 #ifdef SVN_DEBUG_CACHE_MEMBUFFER

Reply via email to