Protect against callbacks removing items from the io tree.
authorEtienne Dechamps <etienne@edechamps.fr>
Sat, 20 Jun 2015 10:41:20 +0000 (11:41 +0100)
committerEtienne Dechamps <etienne@edechamps.fr>
Sat, 20 Jun 2015 13:09:00 +0000 (14:09 +0100)
commitebffa40aa7832459f63801e3a91cc741e6b339a8
tree1b5ed2f2230c7ec4c1a5554d3ce2d5b150270429
parent7f020cf456b327313f0cfa8d103fb14f06f71994
Protect against callbacks removing items from the io tree.

The definition of the splay_each() macro is somewhat complicated for
syntactic reasons. Here's what it does in a more readable way:

  for (splay_node_t* node = tree->head; node;) {
    type* item = node->data;
    splay_node_t* next = node->next;

    // RUN USER BLOCK with (item)

    node = next;
  }

list_each() works in the same way. Since node->next is saved before the
user block runs, this construct supports removing the current item from
within the user block. However, what it does *not* support is removing
*other items* from within the user block, especially the next item.
Indeed, that will invalide the next pointer in the above loop and
therefore result in an invalid pointer dereference.

Unfortunately, there is at least one code path where that unsupported
operation happens. It is located in ack_h(), where the authentication
protocol code detects a double connection (i.e. being connected to
another node twice). Running in the context of a socket read event, this
code will happily terminate the *other* metaconnection, resulting in its
socket being removed from the io tree. If, by misfortune, this other
metaconnection happened to have the next socket FD number (which is
quite possible due to FD reuse - albeit unlikely), and was part of the
io tree (which is quite likely because if that connection is stuck, it
will most likely have pending writes) then this will result in the next
pending io item being destroyed. Invalid pointer dereference ensues.

I did a quick audit of other uses of splay_each() and list_each() and
I believe this is the only scenario in which this "next pointer
invalidation" problem can occur in practice. While this bug has been
there since at least 6bc5d626a8726fc23365ee705761a3c666a08ad4 (November
2012), if not sooner, it happens quite rarely due to the very specific
set of conditions required to trigger it. Nevertheless, it does manage
to crash my central production nodes every other week or so.
src/event.c
src/list.h
src/splay_tree.h