tinc/src/splay_tree.h
Etienne Dechamps 7aca0be0f9 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 6bc5d626a8 (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.
2015-06-26 20:11:31 +02:00

117 lines
3.7 KiB
C

/*
splay_tree.h -- header file for splay_tree.c
Copyright (C) 2004-2013 Guus Sliepen <guus@tinc-vpn.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef __SPLAY_TREE_H__
#define __SPLAY_TREE_H__
typedef struct splay_node_t {
/* Linked list part */
struct splay_node_t *next;
struct splay_node_t *prev;
/* Tree part */
struct splay_node_t *parent;
struct splay_node_t *left;
struct splay_node_t *right;
/* Payload */
void *data;
} splay_node_t;
typedef int (*splay_compare_t)(const void *, const void *);
typedef void (*splay_action_t)(const void *);
typedef void (*splay_action_node_t)(const splay_node_t *);
typedef struct splay_tree_t {
/* Linked list part */
splay_node_t *head;
splay_node_t *tail;
/* Tree part */
splay_node_t *root;
splay_compare_t compare;
splay_action_t delete;
int count;
} splay_tree_t;
/* (De)constructors */
extern splay_tree_t *splay_alloc_tree(splay_compare_t, splay_action_t) __attribute__ ((__malloc__));
extern void splay_free_tree(splay_tree_t *);
extern splay_node_t *splay_alloc_node(void) __attribute__ ((__malloc__));
extern void splay_free_node(splay_tree_t *tree, splay_node_t *);
/* Insertion and deletion */
extern splay_node_t *splay_insert(splay_tree_t *, void *);
extern splay_node_t *splay_insert_node(splay_tree_t *, splay_node_t *);
extern void splay_insert_top(splay_tree_t *, splay_node_t *);
extern void splay_insert_before(splay_tree_t *, splay_node_t *, splay_node_t *);
extern void splay_insert_after(splay_tree_t *, splay_node_t *, splay_node_t *);
extern splay_node_t *splay_unlink(splay_tree_t *, void *);
extern void splay_unlink_node(splay_tree_t *tree, splay_node_t *);
extern void splay_delete(splay_tree_t *, void *);
extern void splay_delete_node(splay_tree_t *, splay_node_t *);
/* Fast tree cleanup */
extern void splay_delete_tree(splay_tree_t *);
/* Searching */
extern void *splay_search(splay_tree_t *, const void *);
extern void *splay_search_closest(splay_tree_t *, const void *, int *);
extern void *splay_search_closest_smaller(splay_tree_t *, const void *);
extern void *splay_search_closest_greater(splay_tree_t *, const void *);
extern splay_node_t *splay_search_node(splay_tree_t *, const void *);
extern splay_node_t *splay_search_closest_node(splay_tree_t *, const void *, int *);
extern splay_node_t *splay_search_closest_node_nosplay(const splay_tree_t *, const void *, int *);
extern splay_node_t *splay_search_closest_smaller_node(splay_tree_t *, const void *);
extern splay_node_t *splay_search_closest_greater_node(splay_tree_t *, const void *);
/* Tree walking */
extern void splay_foreach(const splay_tree_t *, splay_action_t);
extern void splay_foreach_node(const splay_tree_t *, splay_action_t);
/*
Iterates over a tree.
CAUTION: while this construct supports deleting the current item,
it does *not* support deleting *other* nodes while iterating on the tree.
*/
#define splay_each(type, item, tree) (type *item = (type *)1; item; item = NULL) for(splay_node_t *node = (tree)->head, *next; item = node ? node->data : NULL, next = node ? node->next : NULL, node; node = next)
#endif