tor

The Tor anonymity network
git clone https://git.dasho.dev/tor.git
Log | Files | Refs | README | LICENSE

replaycache.c (5820B)


      1 /* Copyright (c) 2012-2021, The Tor Project, Inc. */
      2 /* See LICENSE for licensing information */
      3 
      4 /**
      5 * \file replaycache.c
      6 *
      7 * \brief Self-scrubbing replay cache for rendservice.c
      8 *
      9 * To prevent replay attacks, hidden services need to recognize INTRODUCE2
     10 * cells that they've already seen, and drop them.  If they didn't, then
     11 * sending the same INTRODUCE2 cell over and over would force the hidden
     12 * service to make a huge number of circuits to the same rendezvous
     13 * point, aiding traffic analysis.
     14 *
     15 * (It's not that simple, actually.  We only check for replays in the
     16 * RSA-encrypted portion of the handshake, since the rest of the handshake is
     17 * malleable.)
     18 *
     19 * This module is used from rendservice.c.
     20 */
     21 
     22 #define REPLAYCACHE_PRIVATE
     23 
     24 #include "core/or/or.h"
     25 #include "feature/hs_common/replaycache.h"
     26 
     27 /** Free the replaycache r and all of its entries.
     28 */
     29 void
     30 replaycache_free_(replaycache_t *r)
     31 {
     32  if (!r) {
     33    log_info(LD_BUG, "replaycache_free() called on NULL");
     34    return;
     35  }
     36 
     37  if (r->digests_seen) digest256map_free(r->digests_seen, tor_free_);
     38 
     39  tor_free(r);
     40 }
     41 
     42 /** Allocate a new, empty replay detection cache, where horizon is the time
     43 * for entries to age out and interval is the time after which the cache
     44 * should be scrubbed for old entries.
     45 */
     46 replaycache_t *
     47 replaycache_new(time_t horizon, time_t interval)
     48 {
     49  replaycache_t *r = NULL;
     50 
     51  if (horizon < 0) {
     52    log_info(LD_BUG, "replaycache_new() called with negative"
     53        " horizon parameter");
     54    goto err;
     55  }
     56 
     57  if (interval < 0) {
     58    log_info(LD_BUG, "replaycache_new() called with negative interval"
     59        " parameter");
     60    interval = 0;
     61  }
     62 
     63  r = tor_malloc(sizeof(*r));
     64  r->scrub_interval = interval;
     65  r->scrubbed = 0;
     66  r->horizon = horizon;
     67  r->digests_seen = digest256map_new();
     68 
     69 err:
     70  return r;
     71 }
     72 
     73 /** See documentation for replaycache_add_and_test().
     74 */
     75 STATIC int
     76 replaycache_add_and_test_internal(
     77    time_t present, replaycache_t *r, const void *data, size_t len,
     78    time_t *elapsed)
     79 {
     80  int rv = 0;
     81  uint8_t digest[DIGEST256_LEN];
     82  time_t *access_time;
     83 
     84  /* sanity check */
     85  if (present <= 0 || !r || !data || len == 0) {
     86    log_info(LD_BUG, "replaycache_add_and_test_internal() called with stupid"
     87        " parameters; please fix this.");
     88    goto done;
     89  }
     90 
     91  /* compute digest */
     92  crypto_digest256((char *)digest, (const char *)data, len, DIGEST_SHA256);
     93 
     94  /* check map */
     95  access_time = digest256map_get(r->digests_seen, digest);
     96 
     97  /* seen before? */
     98  if (access_time != NULL) {
     99    /*
    100     * If it's far enough in the past, no hit.  If the horizon is zero, we
    101     * never expire.
    102     */
    103    if (*access_time >= present - r->horizon || r->horizon == 0) {
    104      /* replay cache hit, return 1 */
    105      rv = 1;
    106      /* If we want to output an elapsed time, do so */
    107      if (elapsed) {
    108        if (present >= *access_time) {
    109          *elapsed = present - *access_time;
    110        } else {
    111          /* We shouldn't really be seeing hits from the future, but... */
    112          *elapsed = 0;
    113        }
    114      }
    115    }
    116    /*
    117     * If it's ahead of the cached time, update
    118     */
    119    if (*access_time < present) {
    120      *access_time = present;
    121    }
    122  } else {
    123    /* No, so no hit and update the digest map with the current time */
    124    access_time = tor_malloc(sizeof(*access_time));
    125    *access_time = present;
    126    digest256map_set(r->digests_seen, digest, access_time);
    127  }
    128 
    129  /* now scrub the cache if it's time */
    130  replaycache_scrub_if_needed_internal(present, r);
    131 
    132 done:
    133  return rv;
    134 }
    135 
    136 /** See documentation for replaycache_scrub_if_needed().
    137 */
    138 STATIC void
    139 replaycache_scrub_if_needed_internal(time_t present, replaycache_t *r)
    140 {
    141  digest256map_iter_t *itr = NULL;
    142  const uint8_t *digest;
    143  void *valp;
    144  time_t *access_time;
    145 
    146  /* sanity check */
    147  if (!r || !(r->digests_seen)) {
    148    log_info(LD_BUG, "replaycache_scrub_if_needed_internal() called with"
    149        " stupid parameters; please fix this.");
    150    return;
    151  }
    152 
    153  /* scrub time yet? (scrubbed == 0 indicates never scrubbed before) */
    154  if (present - r->scrubbed < r->scrub_interval && r->scrubbed > 0) return;
    155 
    156  /* if we're never expiring, don't bother scrubbing */
    157  if (r->horizon == 0) return;
    158 
    159  /* okay, scrub time */
    160  itr = digest256map_iter_init(r->digests_seen);
    161  while (!digest256map_iter_done(itr)) {
    162    digest256map_iter_get(itr, &digest, &valp);
    163    access_time = (time_t *)valp;
    164    /* aged out yet? */
    165    if (*access_time < present - r->horizon) {
    166      /* Advance the iterator and remove this one */
    167      itr = digest256map_iter_next_rmv(r->digests_seen, itr);
    168      /* Free the value removed */
    169      tor_free(access_time);
    170    } else {
    171      /* Just advance the iterator */
    172      itr = digest256map_iter_next(r->digests_seen, itr);
    173    }
    174  }
    175 
    176  /* update scrubbed timestamp */
    177  if (present > r->scrubbed) r->scrubbed = present;
    178 }
    179 
    180 /** Test the buffer of length len point to by data against the replay cache r;
    181 * the digest of the buffer will be added to the cache at the current time,
    182 * and the function will return 1 if it was already seen within the cache's
    183 * horizon, or 0 otherwise.
    184 */
    185 int
    186 replaycache_add_and_test(replaycache_t *r, const void *data, size_t len)
    187 {
    188  return replaycache_add_and_test_internal(time(NULL), r, data, len, NULL);
    189 }
    190 
    191 /** Like replaycache_add_and_test(), but if it's a hit also return the time
    192 * elapsed since this digest was last seen.
    193 */
    194 int
    195 replaycache_add_test_and_elapsed(
    196    replaycache_t *r, const void *data, size_t len, time_t *elapsed)
    197 {
    198  return replaycache_add_and_test_internal(time(NULL), r, data, len, elapsed);
    199 }
    200 
    201 /** Scrub aged entries out of r if sufficiently long has elapsed since r was
    202 * last scrubbed.
    203 */
    204 void
    205 replaycache_scrub_if_needed(replaycache_t *r)
    206 {
    207  replaycache_scrub_if_needed_internal(time(NULL), r);
    208 }