/* Copyright (c) 2012-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** * \file replaycache.c * * \brief Self-scrubbing replay cache for rendservice.c * * To prevent replay attacks, hidden services need to recognize INTRODUCE2 * cells that they've already seen, and drop them. If they didn't, then * sending the same INTRODUCE2 cell over and over would force the hidden * service to make a huge number of circuits to the same rendezvous * point, aiding traffic analysis. * * (It's not that simple, actually. We only check for replays in the * RSA-encrypted portion of the handshake, since the rest of the handshake is * malleable.) * * This module is used from rendservice.c. */ #define REPLAYCACHE_PRIVATE #include "or.h" #include "replaycache.h" /** Free the replaycache r and all of its entries. */ void replaycache_free_(replaycache_t *r) { if (!r) { log_info(LD_BUG, "replaycache_free() called on NULL"); return; } if (r->digests_seen) digest256map_free(r->digests_seen, tor_free_); tor_free(r); } /** Allocate a new, empty replay detection cache, where horizon is the time * for entries to age out and interval is the time after which the cache * should be scrubbed for old entries. */ replaycache_t * replaycache_new(time_t horizon, time_t interval) { replaycache_t *r = NULL; if (horizon < 0) { log_info(LD_BUG, "replaycache_new() called with negative" " horizon parameter"); goto err; } if (interval < 0) { log_info(LD_BUG, "replaycache_new() called with negative interval" " parameter"); interval = 0; } r = tor_malloc(sizeof(*r)); r->scrub_interval = interval; r->scrubbed = 0; r->horizon = horizon; r->digests_seen = digest256map_new(); err: return r; } /** See documentation for replaycache_add_and_test() */ STATIC int replaycache_add_and_test_internal( time_t present, replaycache_t *r, const void *data, size_t len, time_t *elapsed) { int rv = 0; uint8_t digest[DIGEST256_LEN]; time_t *access_time; /* sanity check */ if (present <= 0 || !r || !data || len == 0) { log_info(LD_BUG, "replaycache_add_and_test_internal() called with stupid" " parameters; please fix this."); goto done; } /* compute digest */ crypto_digest256((char *)digest, (const char *)data, len, DIGEST_SHA256); /* check map */ access_time = digest256map_get(r->digests_seen, digest); /* seen before? */ if (access_time != NULL) { /* * If it's far enough in the past, no hit. If the horizon is zero, we * never expire. */ if (*access_time >= present - r->horizon || r->horizon == 0) { /* replay cache hit, return 1 */ rv = 1; /* If we want to output an elapsed time, do so */ if (elapsed) { if (present >= *access_time) { *elapsed = present - *access_time; } else { /* We shouldn't really be seeing hits from the future, but... */ *elapsed = 0; } } } /* * If it's ahead of the cached time, update */ if (*access_time < present) { *access_time = present; } } else { /* No, so no hit and update the digest map with the current time */ access_time = tor_malloc(sizeof(*access_time)); *access_time = present; digest256map_set(r->digests_seen, digest, access_time); } /* now scrub the cache if it's time */ replaycache_scrub_if_needed_internal(present, r); done: return rv; } /** See documentation for replaycache_scrub_if_needed() */ STATIC void replaycache_scrub_if_needed_internal(time_t present, replaycache_t *r) { digest256map_iter_t *itr = NULL; const uint8_t *digest; void *valp; time_t *access_time; /* sanity check */ if (!r || !(r->digests_seen)) { log_info(LD_BUG, "replaycache_scrub_if_needed_internal() called with" " stupid parameters; please fix this."); return; } /* scrub time yet? (scrubbed == 0 indicates never scrubbed before) */ if (present - r->scrubbed < r->scrub_interval && r->scrubbed > 0) return; /* if we're never expiring, don't bother scrubbing */ if (r->horizon == 0) return; /* okay, scrub time */ itr = digest256map_iter_init(r->digests_seen); while (!digest256map_iter_done(itr)) { digest256map_iter_get(itr, &digest, &valp); access_time = (time_t *)valp; /* aged out yet? */ if (*access_time < present - r->horizon) { /* Advance the iterator and remove this one */ itr = digest256map_iter_next_rmv(r->digests_seen, itr); /* Free the value removed */ tor_free(access_time); } else { /* Just advance the iterator */ itr = digest256map_iter_next(r->digests_seen, itr); } } /* update scrubbed timestamp */ if (present > r->scrubbed) r->scrubbed = present; } /** Test the buffer of length len point to by data against the replay cache r; * the digest of the buffer will be added to the cache at the current time, * and the function will return 1 if it was already seen within the cache's * horizon, or 0 otherwise. */ int replaycache_add_and_test(replaycache_t *r, const void *data, size_t len) { return replaycache_add_and_test_internal(time(NULL), r, data, len, NULL); } /** Like replaycache_add_and_test(), but if it's a hit also return the time * elapsed since this digest was last seen. */ int replaycache_add_test_and_elapsed( replaycache_t *r, const void *data, size_t len, time_t *elapsed) { return replaycache_add_and_test_internal(time(NULL), r, data, len, elapsed); } /** Scrub aged entries out of r if sufficiently long has elapsed since r was * last scrubbed. */ void replaycache_scrub_if_needed(replaycache_t *r) { replaycache_scrub_if_needed_internal(time(NULL), r); }