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 }