hs-v3: Don't lookup an intro point while cleaning it up

Commit e80893e51b made tor call
hs_service_intro_circ_has_closed() when we mark for close a circuit.

When we cleanup intro points, we iterate over the descriptor's map of intro
points and we can possibly mark for close a circuit. This was problematic
because we would MAP_DEL_CURRENT() the intro point then free it and finally
mark for close the circuit which would lookup the intro point that we just
free in the map we are iterating over.

This can't be done and leads to a use-after-free because the intro point will
be returned successfully due to the fact that we are still in the loop
iterating. In other words, MAP_DEL_CURRENT() followed by a digest256map_get()
of the same object should never be done in the same loop.

Fixes #24595

Signed-off-by: David Goulet <dgoulet@torproject.org>
This commit is contained in:
George Kadianakis 2017-12-12 16:12:30 +02:00 committed by David Goulet
parent 43fb8fc5cb
commit b6fd78ea30

View File

@ -1815,6 +1815,15 @@ intro_point_should_expire(const hs_service_intro_point_t *ip,
static void static void
cleanup_intro_points(hs_service_t *service, time_t now) cleanup_intro_points(hs_service_t *service, time_t now)
{ {
/* List of intro points to close. We can't mark the intro circuits for close
* in the modify loop because doing so calls
* hs_service_intro_circ_has_closed() which does a digest256map_get() on the
* intro points map (that we are iterating over). This can't be done in a
* single iteration after a MAP_DEL_CURRENT, the object will still be
* returned leading to a use-after-free. So, we close the circuits and free
* the intro points after the loop if any. */
smartlist_t *ips_to_free = smartlist_new();
tor_assert(service); tor_assert(service);
/* For both descriptors, cleanup the intro points. */ /* For both descriptors, cleanup the intro points. */
@ -1824,7 +1833,6 @@ cleanup_intro_points(hs_service_t *service, time_t now)
DIGEST256MAP_FOREACH_MODIFY(desc->intro_points.map, key, DIGEST256MAP_FOREACH_MODIFY(desc->intro_points.map, key,
hs_service_intro_point_t *, ip) { hs_service_intro_point_t *, ip) {
const node_t *node = get_node_from_intro_point(ip); const node_t *node = get_node_from_intro_point(ip);
origin_circuit_t *ocirc = hs_circ_service_get_intro_circ(ip);
int has_expired = intro_point_should_expire(ip, now); int has_expired = intro_point_should_expire(ip, now);
/* We cleanup an intro point if it has expired or if we do not know the /* We cleanup an intro point if it has expired or if we do not know the
@ -1845,26 +1853,34 @@ cleanup_intro_points(hs_service_t *service, time_t now)
remember_failing_intro_point(ip, desc, approx_time()); remember_failing_intro_point(ip, desc, approx_time());
} }
/* Remove intro point from descriptor map. We'll add it to the failed /* Remove intro point from descriptor map and add it to the list of
* map if we retried it too many times. */ * ips to free for which we'll also try to close the intro circuit. */
MAP_DEL_CURRENT(key); MAP_DEL_CURRENT(key);
service_intro_point_free(ip); smartlist_add(ips_to_free, ip);
/* XXX: Legacy code does NOT do that, it keeps the circuit open until
* a new descriptor is uploaded and then closed all expiring intro
* point circuit. Here, we close immediately and because we just
* discarded the intro point, a new one will be selected, a new
* descriptor created and uploaded. There is no difference to an
* attacker between the timing of a new consensus and intro point
* rotation (possibly?). */
if (ocirc && !TO_CIRCUIT(ocirc)->marked_for_close) {
/* After this, no new cells will be handled on the circuit. */
circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED);
}
continue;
} }
} DIGEST256MAP_FOREACH_END; } DIGEST256MAP_FOREACH_END;
} FOR_EACH_DESCRIPTOR_END; } FOR_EACH_DESCRIPTOR_END;
/* Go over the intro points to free and close their circuit if any. */
SMARTLIST_FOREACH_BEGIN(ips_to_free, hs_service_intro_point_t *, ip) {
/* See if we need to close the intro point circuit as well */
/* XXX: Legacy code does NOT close circuits like this: it keeps the circuit
* open until a new descriptor is uploaded and then closed all expiring
* intro point circuit. Here, we close immediately and because we just
* discarded the intro point, a new one will be selected, a new descriptor
* created and uploaded. There is no difference to an attacker between the
* timing of a new consensus and intro point rotation (possibly?). */
origin_circuit_t *ocirc = hs_circ_service_get_intro_circ(ip);
if (ocirc && !TO_CIRCUIT(ocirc)->marked_for_close) {
circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED);
}
/* Cleanup the intro point */
service_intro_point_free(ip);
} SMARTLIST_FOREACH_END(ip);
smartlist_free(ips_to_free);
} }
/* Set the next rotation time of the descriptors for the given service for the /* Set the next rotation time of the descriptors for the given service for the