LCOV - code coverage report
Current view: top level - feature/hs_common - replaycache.c (source / functions) Hit Total Coverage
Test: lcov.info Lines: 71 71 100.0 %
Date: 2021-11-24 03:28:48 Functions: 7 7 100.0 %

          Line data    Source code
       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         112 : replaycache_free_(replaycache_t *r)
      31             : {
      32         112 :   if (!r) {
      33           1 :     log_info(LD_BUG, "replaycache_free() called on NULL");
      34           1 :     return;
      35             :   }
      36             : 
      37         111 :   if (r->digests_seen) digest256map_free(r->digests_seen, tor_free_);
      38             : 
      39         111 :   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         114 : replaycache_new(time_t horizon, time_t interval)
      48             : {
      49         114 :   replaycache_t *r = NULL;
      50             : 
      51         114 :   if (horizon < 0) {
      52           2 :     log_info(LD_BUG, "replaycache_new() called with negative"
      53             :         " horizon parameter");
      54           2 :     goto err;
      55             :   }
      56             : 
      57         112 :   if (interval < 0) {
      58           1 :     log_info(LD_BUG, "replaycache_new() called with negative interval"
      59             :         " parameter");
      60           1 :     interval = 0;
      61             :   }
      62             : 
      63         112 :   r = tor_malloc(sizeof(*r));
      64         112 :   r->scrub_interval = interval;
      65         112 :   r->scrubbed = 0;
      66         112 :   r->horizon = horizon;
      67         112 :   r->digests_seen = digest256map_new();
      68             : 
      69         114 :  err:
      70         114 :   return r;
      71             : }
      72             : 
      73             : /** See documentation for replaycache_add_and_test().
      74             :  */
      75             : STATIC int
      76          36 : replaycache_add_and_test_internal(
      77             :     time_t present, replaycache_t *r, const void *data, size_t len,
      78             :     time_t *elapsed)
      79             : {
      80          36 :   int rv = 0;
      81          36 :   uint8_t digest[DIGEST256_LEN];
      82          36 :   time_t *access_time;
      83             : 
      84             :   /* sanity check */
      85          36 :   if (present <= 0 || !r || !data || len == 0) {
      86           2 :     log_info(LD_BUG, "replaycache_add_and_test_internal() called with stupid"
      87             :         " parameters; please fix this.");
      88           2 :     goto done;
      89             :   }
      90             : 
      91             :   /* compute digest */
      92          34 :   crypto_digest256((char *)digest, (const char *)data, len, DIGEST_SHA256);
      93             : 
      94             :   /* check map */
      95          34 :   access_time = digest256map_get(r->digests_seen, digest);
      96             : 
      97             :   /* seen before? */
      98          34 :   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          14 :     if (*access_time >= present - r->horizon || r->horizon == 0) {
     104             :       /* replay cache hit, return 1 */
     105          13 :       rv = 1;
     106             :       /* If we want to output an elapsed time, do so */
     107          13 :       if (elapsed) {
     108           6 :         if (present >= *access_time) {
     109           5 :           *elapsed = present - *access_time;
     110             :         } else {
     111             :           /* We shouldn't really be seeing hits from the future, but... */
     112           1 :           *elapsed = 0;
     113             :         }
     114             :       }
     115             :     }
     116             :     /*
     117             :      * If it's ahead of the cached time, update
     118             :      */
     119          14 :     if (*access_time < present) {
     120           9 :       *access_time = present;
     121             :     }
     122             :   } else {
     123             :     /* No, so no hit and update the digest map with the current time */
     124          20 :     access_time = tor_malloc(sizeof(*access_time));
     125          20 :     *access_time = present;
     126          20 :     digest256map_set(r->digests_seen, digest, access_time);
     127             :   }
     128             : 
     129             :   /* now scrub the cache if it's time */
     130          34 :   replaycache_scrub_if_needed_internal(present, r);
     131             : 
     132          36 :  done:
     133          36 :   return rv;
     134             : }
     135             : 
     136             : /** See documentation for replaycache_scrub_if_needed().
     137             :  */
     138             : STATIC void
     139          37 : replaycache_scrub_if_needed_internal(time_t present, replaycache_t *r)
     140             : {
     141          37 :   digest256map_iter_t *itr = NULL;
     142          37 :   const uint8_t *digest;
     143          37 :   void *valp;
     144          37 :   time_t *access_time;
     145             : 
     146             :   /* sanity check */
     147          37 :   if (!r || !(r->digests_seen)) {
     148           1 :     log_info(LD_BUG, "replaycache_scrub_if_needed_internal() called with"
     149             :         " stupid parameters; please fix this.");
     150          29 :     return;
     151             :   }
     152             : 
     153             :   /* scrub time yet? (scrubbed == 0 indicates never scrubbed before) */
     154          36 :   if (present - r->scrubbed < r->scrub_interval && r->scrubbed > 0) return;
     155             : 
     156             :   /* if we're never expiring, don't bother scrubbing */
     157          25 :   if (r->horizon == 0) return;
     158             : 
     159             :   /* okay, scrub time */
     160           9 :   itr = digest256map_iter_init(r->digests_seen);
     161          18 :   while (!digest256map_iter_done(itr)) {
     162           9 :     digest256map_iter_get(itr, &digest, &valp);
     163           9 :     access_time = (time_t *)valp;
     164             :     /* aged out yet? */
     165           9 :     if (*access_time < present - r->horizon) {
     166             :       /* Advance the iterator and remove this one */
     167           1 :       itr = digest256map_iter_next_rmv(r->digests_seen, itr);
     168             :       /* Free the value removed */
     169           1 :       tor_free(access_time);
     170             :     } else {
     171             :       /* Just advance the iterator */
     172           8 :       itr = digest256map_iter_next(r->digests_seen, itr);
     173             :     }
     174             :   }
     175             : 
     176             :   /* update scrubbed timestamp */
     177           9 :   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           2 : replaycache_add_and_test(replaycache_t *r, const void *data, size_t len)
     187             : {
     188           2 :   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          14 : replaycache_add_test_and_elapsed(
     196             :     replaycache_t *r, const void *data, size_t len, time_t *elapsed)
     197             : {
     198          14 :   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           1 : replaycache_scrub_if_needed(replaycache_t *r)
     206             : {
     207           1 :   replaycache_scrub_if_needed_internal(time(NULL), r);
     208           1 : }
     209             : 

Generated by: LCOV version 1.14