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 :
|