From 15efc25fb502474b8a4f2ee8cb03bd5d185a3f47 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Wed, 12 Oct 2022 20:29:11 +0200 Subject: [PATCH] dns: Make TTLs fuzzy at exit relays This change mitigates DNS-based website oracles by making the time that a domain name is cached uncertain (+- 4 minutes of what's measurable). Resolves TROVE-2021-009. Fixes #40674 --- src/core/or/connection_edge.c | 16 ++++++++++++++++ src/core/or/connection_edge.h | 14 ++++++++++---- src/feature/relay/dns.c | 2 +- src/test/test_dns.c | 31 +++++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 5 deletions(-) diff --git a/src/core/or/connection_edge.c b/src/core/or/connection_edge.c index b96f4eb211..504980c9fd 100644 --- a/src/core/or/connection_edge.c +++ b/src/core/or/connection_edge.c @@ -102,6 +102,7 @@ #include "feature/stats/predict_ports.h" #include "feature/stats/rephist.h" #include "lib/buf/buffers.h" +#include "lib/crypt_ops/crypto_rand.h" #include "lib/crypt_ops/crypto_util.h" #include "core/or/cell_st.h" @@ -498,6 +499,21 @@ clip_dns_ttl(uint32_t ttl) return MAX_DNS_TTL; } +/** Given a TTL (in seconds), determine what TTL an exit relay should use by + * first clipping as usual and then adding some randomness which is sampled + * uniformly at random from [-FUZZY_DNS_TTL, FUZZY_DNS_TTL]. This facilitates + * fuzzy TTLs, which makes it harder to infer when a website was visited via + * side-channels like DNS (see "Website Fingerprinting with Website Oracles"). + * + * Note that this can't underflow because FUZZY_DNS_TTL < MIN_DNS_TTL. + */ +uint32_t +clip_dns_fuzzy_ttl(uint32_t ttl) +{ + return clip_dns_ttl(ttl) + + crypto_rand_uint(1 + 2*FUZZY_DNS_TTL) - FUZZY_DNS_TTL; +} + /** Send a relay end cell from stream conn down conn's circuit, and * remember that we've done so. If this is not a client connection, set the * relay end cell's reason for closing as reason. diff --git a/src/core/or/connection_edge.h b/src/core/or/connection_edge.h index c9433adade..802ca071cd 100644 --- a/src/core/or/connection_edge.h +++ b/src/core/or/connection_edge.h @@ -188,11 +188,9 @@ void connection_ap_warn_and_unmark_if_pending_circ( entry_connection_t *entry_conn, const char *where); -/** Lowest value for DNS ttl that a server should give or a client should - * believe. */ +/** Lowest value for DNS ttl clipping excluding the random addition. */ #define MIN_DNS_TTL (5*60) -/** Highest value for DNS ttl that a server should give or a client should - * believe. */ +/** Highest value for DNS ttl clipping excluding the random addition. */ #define MAX_DNS_TTL (60*60) /** How long do we keep DNS cache entries before purging them (regardless of * their TTL)? */ @@ -200,8 +198,16 @@ void connection_ap_warn_and_unmark_if_pending_circ( /** How long do we cache/tell clients to cache DNS records when no TTL is * known? */ #define DEFAULT_DNS_TTL (30*60) +/** How much should we +- each TTL to make it fuzzy with uniform sampling at + * exits? The value 4 minutes was chosen so that the lowest possible clip is + * 60s. Such low clips were used in the past for all TTLs due to a bug in Tor, + * see "The effect of DNS on Tor's Anonymity" by Greschbach et al. In other + * words, sampling such low clips is unlikely to cause any breakage at exits. + */ +#define FUZZY_DNS_TTL (4*60) uint32_t clip_dns_ttl(uint32_t ttl); +uint32_t clip_dns_fuzzy_ttl(uint32_t ttl); int connection_half_edge_is_valid_data(const smartlist_t *half_conns, streamid_t stream_id); diff --git a/src/feature/relay/dns.c b/src/feature/relay/dns.c index b15e5f30c7..8b684fd9eb 100644 --- a/src/feature/relay/dns.c +++ b/src/feature/relay/dns.c @@ -1637,7 +1637,7 @@ evdns_callback(int result, char type, int count, int ttl, void *addresses, } if (result != DNS_ERR_SHUTDOWN) dns_found_answer(string_address, orig_query_type, - result, &addr, hostname, clip_dns_ttl(ttl)); + result, &addr, hostname, clip_dns_fuzzy_ttl(ttl)); tor_free(arg_); } diff --git a/src/test/test_dns.c b/src/test/test_dns.c index 299321ab64..d2b0777d6b 100644 --- a/src/test/test_dns.c +++ b/src/test/test_dns.c @@ -90,6 +90,36 @@ test_dns_clip_ttl(void *arg) return; } +static void +test_dns_clip_fuzzy_ttl(void *arg) +{ + (void)arg; + + /* Case 0: check that the fuzzy TTL constant is valid + */ + tt_int_op(FUZZY_DNS_TTL, OP_LE, MIN_DNS_TTL); + tt_int_op(FUZZY_DNS_TTL, OP_LE, MAX_DNS_TTL); + + /* Case 1: low clips + */ + for (int i = 0; i < 1024; i++) { + int fuzzy_ttl = clip_dns_fuzzy_ttl(MIN_DNS_TTL - 1); + tt_int_op(fuzzy_ttl, OP_GE, MIN_DNS_TTL-FUZZY_DNS_TTL); + tt_int_op(fuzzy_ttl, OP_LE, MIN_DNS_TTL+FUZZY_DNS_TTL); + } + + /* Case 2: high clips + */ + for (int i = 0; i < 1024; i++) { + int fuzzy_ttl = clip_dns_fuzzy_ttl(MIN_DNS_TTL); + tt_int_op(fuzzy_ttl, OP_GE, MAX_DNS_TTL-FUZZY_DNS_TTL); + tt_int_op(fuzzy_ttl, OP_LE, MAX_DNS_TTL+FUZZY_DNS_TTL); + } + + done: + return; +} + static int resolve_retval = 0; static int resolve_made_conn_pending = 0; static char *resolved_name = NULL; @@ -779,6 +809,7 @@ struct testcase_t dns_tests[] = { TT_FORK, NULL, NULL }, #endif { "clip_ttl", test_dns_clip_ttl, TT_FORK, NULL, NULL }, + { "clip_fuzzy_ttl", test_dns_clip_fuzzy_ttl, TT_FORK, NULL, NULL }, { "resolve", test_dns_resolve, TT_FORK, NULL, NULL }, { "impl_addr_is_ip", test_dns_impl_addr_is_ip, TT_FORK, NULL, NULL }, { "impl_non_exit", test_dns_impl_non_exit, TT_FORK, NULL, NULL },