Merge branch 'bug40827' into 'main'

Fix assert crash on relay-side due to on_circuit backpointer

See merge request tpo/core/tor!737
This commit is contained in:
David Goulet 2023-08-01 20:13:32 +00:00
commit f1fdb58611
4 changed files with 89 additions and 4 deletions

8
changes/bug40827 Normal file
View file

@ -0,0 +1,8 @@
o Major bugfixes (conflux):
- Fix a relay-side assert crash caused by attempts to use a conflux
circuit between circuit close and free, such that no legs were on
the conflux set. Fixed by nulling out the stream's circuit
back-pointer when the last leg is removed. Additional checks and
log messages have been added to detect other cases. Fixes bug 40827;
bugfix on 0.4.8.1-alpha.

View file

@ -536,7 +536,7 @@ conflux_note_cell_sent(conflux_t *cfx, circuit_t *circ, uint8_t relay_command)
/** Find the leg with lowest non-zero curr_rtt_usec, and
* pick it for our current leg. */
static inline void
static inline bool
conflux_pick_first_leg(conflux_t *cfx)
{
conflux_leg_t *min_leg = NULL;
@ -555,8 +555,20 @@ conflux_pick_first_leg(conflux_t *cfx)
} CONFLUX_FOR_EACH_LEG_END(leg);
if (!min_leg) {
// Get the 0th leg; if it does not exist, assert
tor_assert(smartlist_len(cfx->legs) > 0);
// Get the 0th leg; if it does not exist, log the set.
// Bug 40827 managed to hit this, so let's dump the sets
// in case it happens again.
if (BUG(smartlist_len(cfx->legs) <= 0)) {
// Since we have no legs, we have no idea if this is really a client
// or server set. Try to find any that match:
log_warn(LD_BUG, "Matching client sets:");
conflux_log_set(cfx, true);
log_warn(LD_BUG, "Matching server sets:");
conflux_log_set(cfx, false);
log_warn(LD_BUG, "End conflux set dump");
return false;
}
min_leg = smartlist_get(cfx->legs, 0);
tor_assert(min_leg);
if (BUG(min_leg->linked_sent_usec == 0)) {
@ -572,6 +584,8 @@ conflux_pick_first_leg(conflux_t *cfx)
cfx->cells_until_switch = 0;
cfx->curr_leg = min_leg;
return true;
}
/**
@ -589,7 +603,8 @@ conflux_decide_next_circ(conflux_t *cfx)
/* If we don't have a current leg yet, pick one.
* (This is the only non-const operation in this function). */
if (!cfx->curr_leg) {
conflux_pick_first_leg(cfx);
if (!conflux_pick_first_leg(cfx))
return NULL;
}
/* First, check if we can switch. */

View file

@ -1509,6 +1509,49 @@ linked_update_stream_backpointers(circuit_t *circ)
}
}
/** This is called when this circuit is the last leg.
*
* The "on_circuit" pointer is nullified here so it is not given back to the
* conflux subsytem between the circuit mark for close step and actually
* freeing the circuit which is when streams are destroyed.
*
* Reason is that when the connection edge inbuf is packaged in
* connection_edge_package_raw_inbuf(), the on_circuit pointer is used and
* then passed on to conflux to learn which leg to use. However, if the circuit
* was marked prior but not yet freed, there are no more legs remaining which
* leads to a linked circuit being used without legs. No bueno. */
static void
linked_detach_circuit(circuit_t *circ)
{
tor_assert(circ);
tor_assert_nonfatal(circ->conflux);
if (CIRCUIT_IS_ORIGIN(circ)) {
origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ);
tor_assert_nonfatal(circ->purpose == CIRCUIT_PURPOSE_CONFLUX_LINKED);
/* Iterate over stream list using next_stream pointer, until null */
for (edge_connection_t *stream = ocirc->p_streams; stream;
stream = stream->next_stream) {
/* Update the circuit pointer of each stream */
stream->on_circuit = NULL;
}
} else {
or_circuit_t *orcirc = TO_OR_CIRCUIT(circ);
/* Iterate over stream list using next_stream pointer, until null */
for (edge_connection_t *stream = orcirc->n_streams; stream;
stream = stream->next_stream) {
/* Update the circuit pointer of each stream */
stream->on_circuit = NULL;
}
/* Iterate over stream list using next_stream pointer, until null */
for (edge_connection_t *stream = orcirc->resolving_streams; stream;
stream = stream->next_stream) {
/* Update the circuit pointer of each stream */
stream->on_circuit = NULL;
}
}
}
/** Nullify all streams of the given circuit. */
static void
linked_nullify_streams(circuit_t *circ)
@ -1549,6 +1592,7 @@ linked_circuit_closed(circuit_t *circ)
if (CONFLUX_NUM_LEGS(circ->conflux) == 0) {
/* Last leg, remove conflux object from linked set. */
linked_pool_del(circ->conflux->nonce, is_client);
linked_detach_circuit(circ);
} else {
/* If there are other circuits, update streams backpointers and
* nullify the stream lists. We do not free those streams in circuit_free_.

View file

@ -2352,6 +2352,24 @@ connection_edge_package_raw_inbuf(edge_connection_t *conn, int package_partial,
return -1;
}
// Bug 40827: With conflux, we suspect marked circuits were getting here.
// We think we fixed it, but let's add a check and log sets if it still
// happens.
if (BUG(circ->marked_for_close)) {
log_warn(LD_BUG,
"called on circ that's already marked for close at %s:%d.",
circ->marked_for_close_file, circ->marked_for_close);
if (CIRCUIT_IS_CONFLUX(circ)) {
if (circ->conflux) {
conflux_log_set(circ->conflux, CIRCUIT_IS_ORIGIN(circ));
} else {
log_warn(LD_BUG, " - circ is unlinked conflux");
}
}
conn->end_reason = END_STREAM_REASON_INTERNAL;
return -1;
}
if (circuit_consider_stop_edge_reading(circ, cpath_layer))
return 0;