Merge branch 'tor-gitlab/mr/629' into maint-0.4.7

This commit is contained in:
David Goulet 2022-10-26 14:06:33 -04:00
commit b20f72943e
5 changed files with 149 additions and 10 deletions

6
changes/ticket40680 Normal file
View File

@ -0,0 +1,6 @@
o Minor feature (relay, DoS):
- Apply circuit creation anti-DoS defenses if the outbound circuit max cell
queue size is reached too many times. This introduces two new consensus
parameters to control the queue size limit and number of times allowed to
go over that limit. Close ticket 40680.

View File

@ -49,6 +49,7 @@ static int32_t dos_cc_defense_time_period;
/* Keep some stats for the heartbeat so we can report out. */
static uint64_t cc_num_rejected_cells;
static uint32_t cc_num_marked_addrs;
static uint32_t cc_num_marked_addrs_max_queue;
/*
* Concurrent connection denial of service mitigation.
@ -72,6 +73,10 @@ static int32_t dos_conn_connect_defense_time_period =
static uint64_t conn_num_addr_rejected;
static uint64_t conn_num_addr_connect_rejected;
/** Consensus parameter: How many times a client IP is allowed to hit the
* circ_max_cell_queue_size_out limit before being marked. */
static uint32_t dos_num_circ_max_outq;
/*
* General interface of the denial of service mitigation subsystem.
*/
@ -79,6 +84,22 @@ static uint64_t conn_num_addr_connect_rejected;
/* Keep stats for the heartbeat. */
static uint64_t num_single_hop_client_refused;
/** Return the consensus parameter for the outbound circ_max_cell_queue_size
* limit. */
static uint32_t
get_param_dos_num_circ_max_outq(const networkstatus_t *ns)
{
#define DOS_NUM_CIRC_MAX_OUTQ_DEFAULT 3
#define DOS_NUM_CIRC_MAX_OUTQ_MIN 0
#define DOS_NUM_CIRC_MAX_OUTQ_MAX INT32_MAX
/* Update the circuit max cell queue size from the consensus. */
return networkstatus_get_param(ns, "dos_num_circ_max_outq",
DOS_NUM_CIRC_MAX_OUTQ_DEFAULT,
DOS_NUM_CIRC_MAX_OUTQ_MIN,
DOS_NUM_CIRC_MAX_OUTQ_MAX);
}
/* Return true iff the circuit creation mitigation is enabled. We look at the
* consensus for this else a default value is returned. */
MOCK_IMPL(STATIC unsigned int,
@ -258,6 +279,9 @@ set_dos_parameters(const networkstatus_t *ns)
dos_conn_connect_burst = get_param_conn_connect_burst(ns);
dos_conn_connect_defense_time_period =
get_param_conn_connect_defense_time_period(ns);
/* Circuit. */
dos_num_circ_max_outq = get_param_dos_num_circ_max_outq(ns);
}
/* Free everything for the circuit creation DoS mitigation subsystem. */
@ -745,6 +769,69 @@ dos_geoip_entry_init(clientmap_entry_t *geoip_ent)
(uint32_t) approx_time());
}
/** Note that the given channel has sent outbound the maximum amount of cell
* allowed on the next channel. */
void
dos_note_circ_max_outq(const channel_t *chan)
{
tor_addr_t addr;
clientmap_entry_t *entry;
tor_assert(chan);
/* Skip everything if circuit creation defense is disabled. */
if (!dos_cc_enabled) {
goto end;
}
/* Must be a client connection else we ignore. */
if (!channel_is_client(chan)) {
goto end;
}
/* Without an IP address, nothing can work. */
if (!channel_get_addr_if_possible(chan, &addr)) {
goto end;
}
/* We are only interested in client connection from the geoip cache. */
entry = geoip_lookup_client(&addr, NULL, GEOIP_CLIENT_CONNECT);
if (entry == NULL) {
goto end;
}
/* Is the client marked? If yes, just ignore. */
if (entry->dos_stats.cc_stats.marked_until_ts >= approx_time()) {
goto end;
}
/* If max outq parameter is 0, it means disabled, just ignore. */
if (dos_num_circ_max_outq == 0) {
goto end;
}
entry->dos_stats.num_circ_max_cell_queue_size++;
/* This is the detection. If we have reached the maximum amount of times a
* client IP is allowed to reach this limit, mark client. */
if (entry->dos_stats.num_circ_max_cell_queue_size >=
dos_num_circ_max_outq) {
/* Only account for this marked address if this is the first time we block
* it else our counter is inflated with non unique entries. */
if (entry->dos_stats.cc_stats.marked_until_ts == 0) {
cc_num_marked_addrs_max_queue++;
}
log_info(LD_DOS, "Detected outbound max circuit queue from addr: %s",
fmt_addr(&addr));
cc_mark_client(&entry->dos_stats.cc_stats);
/* Reset after being marked so once unmarked, we start back clean. */
entry->dos_stats.num_circ_max_cell_queue_size = 0;
}
end:
return;
}
/* Note down that we've just refused a single hop client. This increments a
* counter later used for the heartbeat. */
void
@ -786,8 +873,10 @@ dos_log_heartbeat(void)
if (dos_cc_enabled) {
smartlist_add_asprintf(elems,
"%" PRIu64 " circuits rejected, "
"%" PRIu32 " marked addresses",
cc_num_rejected_cells, cc_num_marked_addrs);
"%" PRIu32 " marked addresses, "
"%" PRIu32 " marked addresses for max queue",
cc_num_rejected_cells, cc_num_marked_addrs,
cc_num_marked_addrs_max_queue);
} else {
smartlist_add_asprintf(elems, "[DoSCircuitCreationEnabled disabled]");
}

View File

@ -58,6 +58,9 @@ typedef struct dos_client_stats_t {
/* Circuit creation statistics. This is only used if the circuit creation
* subsystem has been enabled (dos_cc_enabled). */
cc_client_stats_t cc_stats;
/** Number of times the circ_max_cell_queue_size limit has been reached. */
uint32_t num_circ_max_cell_queue_size;
} dos_client_stats_t;
/* General API. */
@ -79,6 +82,7 @@ void dos_close_client_conn(const or_connection_t *or_conn);
int dos_should_refuse_single_hop_client(void);
void dos_note_refuse_single_hop_client(void);
void dos_note_circ_max_outq(const channel_t *chan);
/*
* Circuit creation DoS mitigation subsystemn interface.

View File

@ -3134,6 +3134,9 @@ channel_flush_from_first_active_circuit, (channel_t *chan, int max))
}
/* Minimum value is the maximum circuit window size.
*
* This value is set to a lower bound we believe is reasonable with congestion
* control and basic network tunning parameters.
*
* 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
@ -3157,12 +3160,12 @@ channel_flush_from_first_active_circuit, (channel_t *chan, int max))
* 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
#define RELAY_CIRC_CELL_QUEUE_SIZE_MIN 50
/* 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. */
* properly which aren't accounted for in the SENDME window. Default is 2500
* allowed cells in the queue resulting in ~1MB. */
#define RELAY_CIRC_CELL_QUEUE_SIZE_DEFAULT \
(50 * RELAY_CIRC_CELL_QUEUE_SIZE_MIN)
@ -3170,6 +3173,33 @@ channel_flush_from_first_active_circuit, (channel_t *chan, int max))
* every new consensus and controlled by a parameter. */
static int32_t max_circuit_cell_queue_size =
RELAY_CIRC_CELL_QUEUE_SIZE_DEFAULT;
/** Maximum number of cell on an outbound circuit queue. This is updated at
* every new consensus and controlled by a parameter. This default is incorrect
* and won't be used at all except in unit tests. */
static int32_t max_circuit_cell_queue_size_out =
RELAY_CIRC_CELL_QUEUE_SIZE_DEFAULT;
/** Return consensus parameter "circ_max_cell_queue_size". The given ns can be
* NULL. */
static uint32_t
get_param_max_circuit_cell_queue_size(const networkstatus_t *ns)
{
return 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);
}
/** Return consensus parameter "circ_max_cell_queue_size_out". The given ns can
* be NULL. */
static uint32_t
get_param_max_circuit_cell_queue_size_out(const networkstatus_t *ns)
{
return networkstatus_get_param(ns, "circ_max_cell_queue_size_out",
get_param_max_circuit_cell_queue_size(ns),
RELAY_CIRC_CELL_QUEUE_SIZE_MIN,
RELAY_CIRC_CELL_QUEUE_SIZE_MAX);
}
/* Called when the consensus has changed. At this stage, the global consensus
* object has NOT been updated. It is called from
@ -3181,10 +3211,9 @@ relay_consensus_has_changed(const networkstatus_t *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);
get_param_max_circuit_cell_queue_size(ns);
max_circuit_cell_queue_size_out =
get_param_max_circuit_cell_queue_size_out(ns);
}
/** Add <b>cell</b> to the queue of <b>circ</b> writing to <b>chan</b>
@ -3201,6 +3230,7 @@ append_cell_to_circuit_queue(circuit_t *circ, channel_t *chan,
{
or_circuit_t *orcirc = NULL;
cell_queue_t *queue;
int32_t max_queue_size;
int streams_blocked;
int exitward;
if (circ->marked_for_close)
@ -3210,13 +3240,21 @@ append_cell_to_circuit_queue(circuit_t *circ, channel_t *chan,
if (exitward) {
queue = &circ->n_chan_cells;
streams_blocked = circ->streams_blocked_on_n_chan;
max_queue_size = max_circuit_cell_queue_size_out;
} else {
orcirc = TO_OR_CIRCUIT(circ);
queue = &orcirc->p_chan_cells;
streams_blocked = circ->streams_blocked_on_p_chan;
max_queue_size = max_circuit_cell_queue_size;
}
if (PREDICT_UNLIKELY(queue->n >= max_queue_size)) {
/* This DoS defense only applies at the Guard as in the p_chan is likely
* a client IP attacking the network. */
if (exitward && CIRCUIT_IS_ORCIRC(circ)) {
dos_note_circ_max_outq(CONST_TO_OR_CIRCUIT(circ)->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.",

View File

@ -17,6 +17,8 @@ extern uint64_t stats_n_relay_cells_delivered;
extern uint64_t stats_n_circ_max_cell_reached;
void relay_consensus_has_changed(const networkstatus_t *ns);
uint32_t relay_get_param_max_circuit_cell_queue_size(
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);