]> granicus.if.org Git - libevent/commitdiff
Fix an evbuffer crash in evbuffer_remove_buffer()
authorNick Mathewson <nickm@torproject.org>
Wed, 2 Nov 2011 20:09:15 +0000 (16:09 -0400)
committerNick Mathewson <nickm@torproject.org>
Thu, 3 Nov 2011 02:48:16 +0000 (22:48 -0400)
Found by Greg Hazel.

buffer.c

index ce0cdf7062d009028287f7d3ef9bbeaaace05c44..a042a1ede6a6b7682a62039afc41ae39ff4fd167 100644 (file)
--- a/buffer.c
+++ b/buffer.c
@@ -254,6 +254,31 @@ static inline int evbuffer_chains_all_empty(struct evbuffer_chain *chain) {
 }
 #endif
 
+/* Free all trailing chains in 'buf' that are neither pinned nor empty, prior
+ * to replacing them all with a new chain.  Return a pointer to the place
+ * where the new chain will go.
+ *
+ * Internal; requires lock.  The caller must fix up buf->last and buf->first
+ * as needed; they might have been freed.
+ */
+static struct evbuffer_chain **
+evbuffer_free_trailing_empty_chains(struct evbuffer *buf)
+{
+       struct evbuffer_chain **ch = buf->last_with_datap;
+       /* Find the first victim chain.  It might be *last_with_datap */
+       while ((*ch) && ((*ch)->off != 0 || CHAIN_PINNED(*ch)))
+               ch = &(*ch)->next;
+       if (*ch) {
+               EVUTIL_ASSERT(evbuffer_chains_all_empty(*ch));
+               evbuffer_free_all_chains(*ch);
+               *ch = NULL;
+       }
+       return ch;
+}
+
+/* Add a single chain 'chain' to the end of 'buf', freeing trailing empty
+ * chains as necessary.  Requires lock.  Does not schedule callbacks.
+ */
 static void
 evbuffer_chain_insert(struct evbuffer *buf,
     struct evbuffer_chain *chain)
@@ -1104,10 +1129,13 @@ evbuffer_remove_buffer(struct evbuffer *src, struct evbuffer *dst,
 
        if (nread) {
                /* we can remove the chain */
+               struct evbuffer_chain **chp;
+               chp = evbuffer_free_trailing_empty_chains(dst);
+
                if (dst->first == NULL) {
                        dst->first = src->first;
                } else {
-                       dst->last->next = src->first;
+                       *chp = src->first;
                }
                dst->last = previous;
                previous->next = NULL;
@@ -1125,6 +1153,9 @@ evbuffer_remove_buffer(struct evbuffer *src, struct evbuffer *dst,
        chain->off -= datlen;
        nread += datlen;
 
+       /* You might think we would want to increment dst->n_add_for_cb
+        * here too.  But evbuffer_add above already took care of that.
+        */
        src->total_len -= nread;
        src->n_del_for_cb += nread;