Tor 0.4.9.13
Loading...
Searching...
No Matches
replaycache.c
Go to the documentation of this file.
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"
26
27/** Free the replaycache r and all of its entries.
28 */
29void
30replaycache_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 */
46replaycache_t *
47replaycache_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/** Check a digest without modifying the cache. Return its stored timestamp
74 * through access_time, even if expired, so the caller can refresh it. */
75static int
76replaycache_test_and_elapsed_internal(time_t present, const replaycache_t *r,
77 const uint8_t *digest, time_t *elapsed,
78 time_t **access_time)
79{
80 *access_time = digest256map_get(r->digests_seen, digest);
81 if (!*access_time || (r->horizon && **access_time < present - r->horizon)) {
82 return 0;
83 }
84 if (elapsed) {
85 *elapsed = present >= **access_time ? present - **access_time : 0;
86 }
87 return 1;
88}
89
90/** See documentation for replaycache_add_and_test().
91 */
92STATIC int
94 time_t present, replaycache_t *r, const void *data, size_t len,
95 time_t *elapsed)
96{
97 int rv = 0;
98 uint8_t digest[DIGEST256_LEN];
99 time_t *access_time;
100
101 /* sanity check */
102 if (present <= 0 || !r || !data || len == 0) {
103 log_info(LD_BUG, "replaycache_add_and_test_internal() called with stupid"
104 " parameters; please fix this.");
105 goto done;
106 }
107
108 /* compute digest */
109 crypto_digest256((char *)digest, (const char *)data, len, DIGEST_SHA256);
110
111 rv = replaycache_test_and_elapsed_internal(present, r, digest, elapsed,
112 &access_time);
113
114 /* seen before? */
115 if (access_time != NULL) {
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 */
131
132 done:
133 return rv;
134}
135
136/** See documentation for replaycache_scrub_if_needed().
137 */
138STATIC void
139replaycache_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 */
185int
186replaycache_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/** Test for a cached digest without inserting, refreshing, or scrubbing any
192 * entries. On a hit, optionally return the time since it was last added. */
193int
194replaycache_test_and_elapsed(const replaycache_t *r, const void *data,
195 size_t len, time_t *elapsed)
196{
197 uint8_t digest[DIGEST256_LEN];
198 time_t *access_time;
199
200 tor_assert(r);
201 tor_assert(data);
202 tor_assert(len);
203
204 crypto_digest256((char *)digest, data, len, DIGEST_SHA256);
205 return replaycache_test_and_elapsed_internal(time(NULL), r, digest, elapsed,
206 &access_time);
207}
208
209/** Like replaycache_add_and_test(), but if it's a hit also return the time
210 * elapsed since this digest was last seen.
211 */
212int
214 replaycache_t *r, const void *data, size_t len, time_t *elapsed)
215{
216 return replaycache_add_and_test_internal(time(NULL), r, data, len, elapsed);
217}
218
219/** Scrub aged entries out of r if sufficiently long has elapsed since r was
220 * last scrubbed.
221 */
222void
224{
226}
227
228/** Return the number of entries currently stored in the given cache `r`. */
229size_t
230replay_cache_count(const replaycache_t *r)
231{
232 if (!r) {
233 return 0;
234 }
235 return digest256map_size(r->digests_seen);
236}
int crypto_digest256(char *digest, const char *m, size_t len, digest_algorithm_t algorithm)
#define DIGEST256_LEN
#define LD_BUG
Definition log.h:86
void tor_free_(void *mem)
Definition malloc.c:227
#define tor_free(p)
Definition malloc.h:56
Master header file for Tor-specific functionality.
static int replaycache_test_and_elapsed_internal(time_t present, const replaycache_t *r, const uint8_t *digest, time_t *elapsed, time_t **access_time)
Definition replaycache.c:76
size_t replay_cache_count(const replaycache_t *r)
void replaycache_free_(replaycache_t *r)
Definition replaycache.c:30
void replaycache_scrub_if_needed(replaycache_t *r)
int replaycache_add_test_and_elapsed(replaycache_t *r, const void *data, size_t len, time_t *elapsed)
STATIC int replaycache_add_and_test_internal(time_t present, replaycache_t *r, const void *data, size_t len, time_t *elapsed)
Definition replaycache.c:93
int replaycache_test_and_elapsed(const replaycache_t *r, const void *data, size_t len, time_t *elapsed)
replaycache_t * replaycache_new(time_t horizon, time_t interval)
Definition replaycache.c:47
int replaycache_add_and_test(replaycache_t *r, const void *data, size_t len)
STATIC void replaycache_scrub_if_needed_internal(time_t present, replaycache_t *r)
Header file for replaycache.c.
#define STATIC
Definition testsupport.h:32
#define tor_assert(expr)
Definition util_bug.h:103