diff --git a/changes/bug25226 b/changes/bug25226 new file mode 100644 index 0000000000..b594a7a424 --- /dev/null +++ b/changes/bug25226 @@ -0,0 +1,4 @@ + o Major bugfixes (relay, denial of service): + - Impose a limit on circuit cell queue size. The limit can be controlled by + a consensus parameter. Fixes bug 25226; bugfix on 0.2.4.14-alpha. + diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c index 235b95b704..e03d9e6fe5 100644 --- a/src/or/networkstatus.c +++ b/src/or/networkstatus.c @@ -1603,6 +1603,7 @@ notify_before_networkstatus_changes(const networkstatus_t *old_c, { notify_control_networkstatus_changed(old_c, new_c); dos_consensus_has_changed(new_c); + relay_consensus_has_changed(new_c); } /* Called after a new consensus has been put in the global state. It is safe diff --git a/src/or/or.h b/src/or/or.h index c0e1ffff48..c5a039e939 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -3500,12 +3500,6 @@ typedef struct or_circuit_t { * exit-ward queues of this circuit; reset every time when writing * buffer stats to disk. */ uint64_t total_cell_waiting_time; - - /** Maximum cell queue size for a middle relay; this is stored per circuit - * so append_cell_to_circuit_queue() can adjust it if it changes. If set - * to zero, it is initialized to the default value. - */ - uint32_t max_middle_cells; } or_circuit_t; #if REND_COOKIE_LEN != DIGEST_LEN diff --git a/src/or/relay.c b/src/or/relay.c index c6f030945b..72a902d8b4 100644 --- a/src/or/relay.c +++ b/src/or/relay.c @@ -100,9 +100,6 @@ static void adjust_exit_policy_from_exitpolicy_failure(origin_circuit_t *circ, entry_connection_t *conn, node_t *node, const tor_addr_t *addr); -#if 0 -static int get_max_middle_cells(void); -#endif /** Stop reading on edge connections when we have this many cells * waiting on the appropriate queue. */ @@ -2775,6 +2772,60 @@ channel_flush_from_first_active_circuit, (channel_t *chan, int max)) return n_flushed; } +/* Minimum value is the maximum circuit window size. + * + * SENDME cells makes it that we can control how many cells can be inflight on + * a circuit from end to end. This logic makes it that on any circuit cell + * queue, we have a maximum of cells possible. + * + * Because the Tor protocol allows for a client to exit at any hop in a + * circuit and a circuit can be of a maximum of 8 hops, so in theory the + * normal worst case will be the circuit window start value times the maximum + * number of hops (8). Having more cells then that means something is wrong. + * + * However, because padding cells aren't counted in the package window, we set + * the maximum size to a reasonably large size for which we expect that we'll + * never reach in theory. And if we ever do because of future changes, we'll + * be able to control it with a consensus parameter. + * + * XXX: Unfortunately, END cells aren't accounted for in the circuit window + * which means that for instance if a client opens 8001 streams, the 8001 + * following END cells will queue up in the circuit which will get closed if + * the max limit is 8000. Which is sad because it is allowed by the Tor + * protocol. But, we need an upper bound on circuit queue in order to avoid + * DoS memory pressure so the default size is a middle ground between not + * having any limit and having a very restricted one. This is why we can also + * control it through a consensus parameter. */ +#define RELAY_CIRC_CELL_QUEUE_SIZE_MIN CIRCWINDOW_START_MAX +/* We can't have a consensus parameter above this value. */ +#define RELAY_CIRC_CELL_QUEUE_SIZE_MAX INT32_MAX +/* Default value is set to a large value so we can handle padding cells + * properly which aren't accounted for in the SENDME window. Default is 50000 + * allowed cells in the queue resulting in ~25MB. */ +#define RELAY_CIRC_CELL_QUEUE_SIZE_DEFAULT \ + (50 * RELAY_CIRC_CELL_QUEUE_SIZE_MIN) + +/* The maximum number of cell a circuit queue can contain. This is updated at + * every new consensus and controlled by a parameter. */ +static int32_t max_circuit_cell_queue_size = + RELAY_CIRC_CELL_QUEUE_SIZE_DEFAULT; + +/* Called when the consensus has changed. At this stage, the global consensus + * object has NOT been updated. It is called from + * notify_before_networkstatus_changes(). */ +void +relay_consensus_has_changed(const networkstatus_t *ns) +{ + tor_assert(ns); + + /* Update the circuit max cell queue size from the consensus. */ + max_circuit_cell_queue_size = + networkstatus_get_param(ns, "circ_max_cell_queue_size", + RELAY_CIRC_CELL_QUEUE_SIZE_DEFAULT, + RELAY_CIRC_CELL_QUEUE_SIZE_MIN, + RELAY_CIRC_CELL_QUEUE_SIZE_MAX); +} + /** Add cell to the queue of circ writing to chan * transmitting in direction. * @@ -2804,6 +2855,16 @@ append_cell_to_circuit_queue(circuit_t *circ, channel_t *chan, streams_blocked = circ->streams_blocked_on_p_chan; } + if (PREDICT_UNLIKELY(queue->n >= max_circuit_cell_queue_size)) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "%s circuit has %d cells in its queue, maximum allowed is %d. " + "Closing circuit for safety reasons.", + (exitward) ? "Outbound" : "Inbound", queue->n, + max_circuit_cell_queue_size); + circuit_mark_for_close(circ, END_CIRC_REASON_RESOURCELIMIT); + return; + } + /* Very important that we copy to the circuit queue because all calls to * this function use the stack for the cell memory. */ cell_queue_append_packed_copy(circ, queue, exitward, cell, diff --git a/src/or/relay.h b/src/or/relay.h index cc78758883..b4c0bff055 100644 --- a/src/or/relay.h +++ b/src/or/relay.h @@ -15,6 +15,7 @@ extern uint64_t stats_n_relay_cells_relayed; extern uint64_t stats_n_relay_cells_delivered; +void relay_consensus_has_changed(const networkstatus_t *ns); int circuit_receive_relay_cell(cell_t *cell, circuit_t *circ, cell_direction_t cell_direction); size_t cell_queues_get_total_allocation(void);