Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug26294 #1163

Closed
wants to merge 9 commits into from
hs_service: Implement the new replay cache behavior for v3.
We now want to wipe the replay cache after a certain number of introductions
but keep the intro point intact.

Also increase the size of the replay cache to fit 150k-300k introductions.
  • Loading branch information
asn-d6 committed Jul 2, 2019
commit 6ef1ac5eed85d7cf3cafa1797dc1003912d1a63c
@@ -1060,10 +1060,30 @@ typedef struct rend_encoded_v2_service_descriptor_t
* introduction point. See also rend_intro_point_t.unreachable_count. */
#define MAX_INTRO_POINT_REACHABILITY_FAILURES 5

/** The minimum and maximum number of distinct INTRODUCE2 cells which a
* hidden service's introduction point will receive before it begins to
* expire. */
#define INTRO_POINT_MIN_LIFETIME_INTRODUCTIONS 16384
/** The minimum and maximum number of distinct INTRODUCE2 cells which an onion
* service's introduction point will receive before it refreshes its
* per-intro-point replay cache (which tracks the encrypted part of INTRO2).
*
* The actual limit number is sampled uniformly between the minimum and
* maximum.
*
* This means that after the limit gets hit and the replay cache gets
* refreshed, an attacker can succeed in replaying an INTRODUCE2 cell. We
* believe that a small number of replays does not hurt an onion service.
*
*
* Now, in terms of memory load, we can consider a replay cache to be a
* digest256map_t. Each entry of a digest256map is composed by:
* - a key (32 bytes)
* - a void * (8 bytes)
* - an HT_ENTRY (a pointer and an unsigned: 8+8 bytes)
* For a total of about 56 bytes per entry (plus the replaycache headers
* which we will consider free for the purposes of this calculation).
*
* So if we cycle the replay cache after 150k to 300k introductions, this
* means that the replay cache size will be between 8.4MB and 16.8MB.
*/
#define INTRO_POINT_MIN_LIFETIME_INTRODUCTIONS 150000
/* Double the minimum value so the interval is [min, min * 2]. */
#define INTRO_POINT_MAX_LIFETIME_INTRODUCTIONS \
(INTRO_POINT_MIN_LIFETIME_INTRODUCTIONS * 2)
@@ -2289,19 +2289,12 @@ update_all_descriptors_intro_points(time_t now)
/* Return true iff the given intro point has expired that is it has been used
* for too long or we've reached our max seen INTRODUCE2 cell. */
STATIC int
intro_point_should_expire(const hs_service_intro_point_t *ip,
intro_point_should_expire(hs_service_intro_point_t *ip,

This comment has been minimized.

@teor2345

teor2345 Jul 2, 2019
Contributor

You could probably leave the const in here, if you want.

This comment has been minimized.

@asn-d6

asn-d6 Jul 3, 2019
Author Member

Ouch that was left accidentally in from previous experimentations. Good catch.

This comment has been minimized.

@asn-d6

asn-d6 Aug 5, 2019
Author Member

Fixed in 45f5900.

time_t now)
{
tor_assert(ip);

if (ip->time_to_expire <= now) {
goto expired;
}

/* Not expiring. */
return 0;
expired:
return 1;
return ip->time_to_expire <= now;
}

/* Go over the given set of intro points for each service and remove any
@@ -2510,6 +2503,32 @@ rotate_all_descriptors(time_t now)
} FOR_EACH_SERVICE_END;
}

/** Check the replay cache of this intro point, and wipe it if needed. */
STATIC void
check_intro_point_replay_cache(hs_service_intro_point_t *ip)
{
if ((size_t) replaycache_size(ip->replay_cache) >= ip->introduce2_max) {
replaycache_free(ip->replay_cache);
ip->replay_cache = replaycache_new(0, 0);

This comment has been minimized.

@dgoulet-tor

dgoulet-tor Jul 4, 2019
Contributor

Can I ask you to add a log_info() here so we know when a rotation happened?

I'm asking because for experimenting on our test-bed, I had to add a print here. Thanks!

This comment has been minimized.

@asn-d6

asn-d6 Aug 5, 2019
Author Member

Fixed in 17905ba.

}
}

/** Make sure that all intro point replay caches are well maintained. */
static void
maintain_intro_point_replay_caches(hs_service_t *service)
{
tor_assert(service);

/* For both descriptors, see if any intro points have passed their
* introduction limit and their replay cache needs to be refreshed. */
FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
DIGEST256MAP_FOREACH_MODIFY(desc->intro_points.map, key,
hs_service_intro_point_t *, ip) {
check_intro_point_replay_cache(ip);
} DIGEST256MAP_FOREACH_END;
} FOR_EACH_DESCRIPTOR_END;
}

/* Scheduled event run from the main loop. Make sure all our services are up
* to date and ready for the other scheduled events. This includes looking at
* the introduction points status and descriptor rotation time. */
@@ -2534,6 +2553,9 @@ run_housekeeping_event(time_t now)
/* Cleanup invalid intro points from the service descriptor. */
cleanup_intro_points(service, now);

/* Maintain the intro point replay caches */
maintain_intro_point_replay_caches(service);

/* Remove expired failing intro point from the descriptor failed list. We
* reset them at each INTRO_CIRC_RETRY_PERIOD. */
remove_expired_failing_intro(service, now);
@@ -71,7 +71,9 @@ typedef struct hs_service_intro_point_t {

/* Replay cache recording the encrypted part of an INTRODUCE2 cell that the
* circuit associated with this intro point has received. This is used to
* prevent replay attacks. */
* prevent replay attacks. See INTRO_POINT_MIN_LIFETIME_INTRODUCTIONS for
* more details.
*/
replaycache_t *replay_cache;
} hs_service_intro_point_t;

@@ -395,7 +397,7 @@ STATIC const node_t *
get_node_from_intro_point(const hs_service_intro_point_t *ip);
STATIC int can_service_launch_intro_circuit(hs_service_t *service,
time_t now);
STATIC int intro_point_should_expire(const hs_service_intro_point_t *ip,
STATIC int intro_point_should_expire(hs_service_intro_point_t *ip,
time_t now);
STATIC void run_housekeeping_event(time_t now);
STATIC void rotate_all_descriptors(time_t now);
@@ -433,6 +435,8 @@ STATIC int service_authorized_client_config_equal(

STATIC void service_clear_config(hs_service_config_t *config);

STATIC void check_intro_point_replay_cache(hs_service_intro_point_t *ip);

#endif /* defined(HS_SERVICE_PRIVATE) */

#endif /* !defined(TOR_HS_SERVICE_H) */
@@ -2323,12 +2323,11 @@ test_intro_replay_cache_expiration(void *arg)

/* 2) Now we want to test that our replaycache has a maximum intro2 limit.
*
* Start using a different rend cookie for every intro2 cell and send lots of
* them. */
* Send a lot of INTRO2 cells and see that the replaycache is filled
* properly. Don't pass the maximum introduction limit otherwise the
* replaycache is gonna get wiped. */
for (i = 0 ; i < 9 ; i++) {
/* Send enough intro2 so that it doesn't expire */
retval = intro_point_should_expire(ip, approx_time());
tt_int_op(retval, OP_EQ, 0);
check_intro_point_replay_cache(ip);

retval = helper_create_introduce2(ip, cell);
tt_int_op(retval, OP_GE, 0);
@@ -2341,9 +2340,10 @@ test_intro_replay_cache_expiration(void *arg)
/* Check the contents of the replaycache */
tt_int_op(replaycache_size(ip->replay_cache), OP_EQ, 10);

/* That last intro2 should make it expire: */
retval = intro_point_should_expire(ip, approx_time());
tt_int_op(retval, OP_EQ, 1);
/* Now that we have 10 INTRO2, the replay cache has surpassed its limit and
* should be wiped: */
check_intro_point_replay_cache(ip);
tt_int_op(replaycache_size(ip->replay_cache), OP_EQ, 0);

done:
tor_free(mock_node.ri->onion_curve25519_pkey);