tor

The Tor anonymity network
git clone https://git.dasho.dev/tor.git
Log | Files | Refs | README | LICENSE

commit b628ffeb6234c58d0921756b9a34ac601870f09a
parent 08e872ef51be562dc5f7c59d6e46d7f6006b3dd7
Author: David Goulet <dgoulet@torproject.org>
Date:   Wed, 18 Dec 2024 10:24:28 -0500

hs: Add downloaded counter to an HSDir cache entry

This adds a counter for the number of times a descriptor is downloaded from an
HSDir. Future commit will change the OOM subsystem to clean that cache based on
the lowest downloaded counts instead of time in cache.

In order to raise the bar even more for an attacker, the downloaded counter is
only marked when the directory request stream is closed. To pull this off, the
HS identifier on the directory connection is populated with the blinded key
requested (only on success). Finally, when the connection closes, we can then
lookup the cache entry with it and increment the counter.

Part of #40996

Signed-off-by: David Goulet <dgoulet@torproject.org>

Diffstat:
Msrc/feature/dircache/dircache.c | 13+++++++++++++
Msrc/feature/dircommon/dir_connection_st.h | 4+++-
Msrc/feature/dircommon/directory.c | 12++++++++++++
Msrc/feature/hs/hs_cache.c | 16++++++++++++++++
Msrc/feature/hs/hs_cache.h | 6++++++
Msrc/feature/hs/hs_ident.c | 10++++++++++
Msrc/feature/hs/hs_ident.h | 2++
7 files changed, 62 insertions(+), 1 deletion(-)

diff --git a/src/feature/dircache/dircache.c b/src/feature/dircache/dircache.c @@ -26,6 +26,7 @@ #include "feature/dircommon/directory.h" #include "feature/dircommon/fp_pair.h" #include "feature/hs/hs_cache.h" +#include "feature/hs/hs_ident.h" #include "feature/nodelist/authcert.h" #include "feature/nodelist/networkstatus.h" #include "feature/nodelist/routerlist.h" @@ -34,6 +35,7 @@ #include "feature/stats/geoip_stats.h" #include "feature/stats/rephist.h" #include "lib/compress/compress.h" +#include "lib/crypt_ops/crypto_format.h" #include "feature/dircache/cached_dir_st.h" #include "feature/dircommon/dir_connection_st.h" @@ -1381,6 +1383,17 @@ handle_get_hs_descriptor_v3(dir_connection_t *conn, write_http_response_header(conn, strlen(desc_str), NO_METHOD, 0); connection_buf_add(desc_str, strlen(desc_str), TO_CONN(conn)); + /* We have successfully written the descriptor on the connection outbuf so + * save this query identifier into the dir_connection_t in order + * to associate it to the descriptor when closing. */ + { + /* Decode blinded key. This is certain to work else + * hs_cache_lookup_as_dir() would have failed. */ + ed25519_public_key_t blinded_key; + ed25519_public_from_base64(&blinded_key, pubkey_str); + conn->hs_ident = hs_ident_server_dir_conn_new(&blinded_key); + } + done: return 0; } diff --git a/src/feature/dircommon/dir_connection_st.h b/src/feature/dircommon/dir_connection_st.h @@ -44,7 +44,9 @@ struct dir_connection_t { /* Hidden service connection identifier for dir connections: Used by HS client-side code to fetch HS descriptors, and by the service-side code to - upload descriptors. */ + upload descriptors. Also used by the HSDir, setting only the blinded key, + in order to locate back the descriptor in the cache once the dir stream is + closed. */ struct hs_ident_dir_conn_t *hs_ident; /** If this is a one-hop connection, tracks the state of the directory guard diff --git a/src/feature/dircommon/directory.c b/src/feature/dircommon/directory.c @@ -16,6 +16,7 @@ #include "feature/dirclient/dirclient.h" #include "feature/dircommon/directory.h" #include "feature/dircommon/fp_pair.h" +#include "feature/hs/hs_cache.h" #include "feature/stats/geoip_stats.h" #include "lib/compress/compress.h" @@ -492,6 +493,17 @@ connection_dir_about_to_close(dir_connection_t *dir_conn) connection_dir_client_request_failed(dir_conn); } + /* If we are an HSDir, mark the corresponding descriptor as downloaded. This + * is needed for the OOM cache cleanup. + * + * This is done when the direction connection is closed in order to raise the + * attack cost of filling the cache with bogus descriptors. That attacker + * would need to increase that downloaded counter for the attack to be + * successful which is expensive. */ + if (conn->purpose == DIR_PURPOSE_SERVER && dir_conn->hs_ident) { + hs_cache_mark_dowloaded_as_dir(dir_conn->hs_ident); + } + connection_dir_client_refetch_hsdesc_if_needed(dir_conn); } diff --git a/src/feature/hs/hs_cache.c b/src/feature/hs/hs_cache.c @@ -334,6 +334,22 @@ hs_cache_lookup_as_dir(uint32_t version, const char *query, return found; } +/** Using the given directory identifier, lookup the descriptor in our cache + * and if present, increment the downloaded counter. This is done when the + * directory connection fetching this descriptor is closed. */ +void +hs_cache_mark_dowloaded_as_dir(const hs_ident_dir_conn_t *ident) +{ + hs_cache_dir_descriptor_t *entry; + + tor_assert(ident); + + entry = lookup_v3_desc_as_dir(ident->blinded_pk.pubkey); + if (entry) { + entry->n_downloaded++; + } +} + /** Clean all directory caches using the current time now. */ void hs_cache_clean_as_dir(time_t now) diff --git a/src/feature/hs/hs_cache.h b/src/feature/hs/hs_cache.h @@ -13,6 +13,7 @@ #include "feature/hs/hs_common.h" #include "feature/hs/hs_descriptor.h" +#include "feature/hs/hs_ident.h" #include "feature/rend/rendcommon.h" #include "feature/nodelist/torcert.h" @@ -68,6 +69,10 @@ typedef struct hs_cache_dir_descriptor_t { /** Encoded descriptor which is basically in text form. It's a NUL terminated * string thus safe to strlen(). */ char *encoded_desc; + /** How many times this descriptor has been downloaded. We use this as an + * heuristic for the OOM cache cleaning. It is very large so we avoid an kind + * of possible wrapping. */ + uint64_t n_downloaded; } hs_cache_dir_descriptor_t; /* Public API */ @@ -92,6 +97,7 @@ unsigned int hs_cache_get_max_descriptor_size(void); int hs_cache_store_as_dir(const char *desc); int hs_cache_lookup_as_dir(uint32_t version, const char *query, const char **desc_out); +void hs_cache_mark_dowloaded_as_dir(const hs_ident_dir_conn_t *ident); const hs_descriptor_t * hs_cache_lookup_as_client(const struct ed25519_public_key_t *key); diff --git a/src/feature/hs/hs_ident.c b/src/feature/hs/hs_ident.c @@ -62,6 +62,16 @@ hs_ident_dir_conn_free_(hs_ident_dir_conn_t *ident) tor_free(ident); } +/** Return a newly allocated HS directory connection identifier that is meant + * for the server side (HSDir). Only the blinded key is known by the HSDir. */ +hs_ident_dir_conn_t * +hs_ident_server_dir_conn_new(const ed25519_public_key_t *blinded_pk) +{ + hs_ident_dir_conn_t *ident = tor_malloc_zero(sizeof(*ident)); + ed25519_pubkey_copy(&ident->blinded_pk, blinded_pk); + return ident; +} + /** Initialized the allocated ident object with identity_pk and blinded_pk. * None of them can be NULL since a valid directory connection identifier must * have all fields set. */ diff --git a/src/feature/hs/hs_ident.h b/src/feature/hs/hs_ident.h @@ -128,6 +128,8 @@ void hs_ident_dir_conn_free_(hs_ident_dir_conn_t *ident); void hs_ident_dir_conn_init(const ed25519_public_key_t *identity_pk, const ed25519_public_key_t *blinded_pk, hs_ident_dir_conn_t *ident); +hs_ident_dir_conn_t *hs_ident_server_dir_conn_new( + const ed25519_public_key_t *blinded_pk); /* Edge connection identifier API. */ hs_ident_edge_conn_t *hs_ident_edge_conn_new(