tor

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

hs_pow.c (18946B)


      1 /* Copyright (c) 2017-2020, The Tor Project, Inc. */
      2 /* See LICENSE for licensing information */
      3 
      4 /**
      5 * \file hs_pow.c
      6 * \brief Contains code to handle proof-of-work computations
      7 * when a hidden service is defending against DoS attacks.
      8 **/
      9 
     10 #include <stdio.h>
     11 
     12 #include "core/or/or.h"
     13 #include "app/config/config.h"
     14 #include "ext/ht.h"
     15 #include "ext/compat_blake2.h"
     16 #include "core/or/circuitlist.h"
     17 #include "core/or/origin_circuit_st.h"
     18 #include "ext/equix/include/equix.h"
     19 #include "feature/hs/hs_cache.h"
     20 #include "feature/hs/hs_descriptor.h"
     21 #include "feature/hs/hs_circuitmap.h"
     22 #include "feature/hs/hs_client.h"
     23 #include "feature/hs/hs_pow.h"
     24 #include "lib/crypt_ops/crypto_rand.h"
     25 #include "lib/crypt_ops/crypto_format.h"
     26 #include "lib/arch/bytes.h"
     27 #include "lib/cc/ctassert.h"
     28 #include "core/mainloop/cpuworker.h"
     29 #include "lib/evloop/workqueue.h"
     30 #include "lib/time/compat_time.h"
     31 
     32 /** Replay cache set up */
     33 /** Cache entry for (nonce, seed) replay protection. */
     34 typedef struct nonce_cache_entry_t {
     35  HT_ENTRY(nonce_cache_entry_t) node;
     36  struct {
     37    uint8_t nonce[HS_POW_NONCE_LEN];
     38    uint8_t seed_head[HS_POW_SEED_HEAD_LEN];
     39  } bytes;
     40 } nonce_cache_entry_t;
     41 
     42 /** Return true if the two (nonce, seed) replay cache entries are the same */
     43 static inline int
     44 nonce_cache_entries_eq_(const struct nonce_cache_entry_t *entry1,
     45                        const struct nonce_cache_entry_t *entry2)
     46 {
     47  return fast_memeq(&entry1->bytes, &entry2->bytes, sizeof entry1->bytes);
     48 }
     49 
     50 /** Hash function to hash the (nonce, seed) tuple entry. */
     51 static inline unsigned
     52 nonce_cache_entry_hash_(const struct nonce_cache_entry_t *ent)
     53 {
     54  return (unsigned)siphash24g(&ent->bytes, sizeof ent->bytes);
     55 }
     56 
     57 static HT_HEAD(nonce_cache_table_ht, nonce_cache_entry_t)
     58  nonce_cache_table = HT_INITIALIZER();
     59 
     60 HT_PROTOTYPE(nonce_cache_table_ht, nonce_cache_entry_t, node,
     61             nonce_cache_entry_hash_, nonce_cache_entries_eq_);
     62 
     63 HT_GENERATE2(nonce_cache_table_ht, nonce_cache_entry_t, node,
     64             nonce_cache_entry_hash_, nonce_cache_entries_eq_, 0.6,
     65             tor_reallocarray_, tor_free_);
     66 
     67 /** This is a callback used to check replay cache entries against a provided
     68 * seed head, or NULL to operate on the entire cache. Matching entries return
     69 * 1 and their internal cache entry is freed, non-matching entries return 0. */
     70 static int
     71 nonce_cache_entry_match_seed_and_free(nonce_cache_entry_t *ent, void *data)
     72 {
     73  if (data == NULL ||
     74      fast_memeq(ent->bytes.seed_head, data, HS_POW_SEED_HEAD_LEN)) {
     75    tor_free(ent);
     76    return 1;
     77  }
     78  return 0;
     79 }
     80 
     81 /** Helper: Increment a given nonce and set it in the challenge at the right
     82 * offset. Use by the solve function. */
     83 static inline void
     84 increment_and_set_nonce(uint8_t *nonce, uint8_t *challenge)
     85 {
     86  for (unsigned i = 0; i < HS_POW_NONCE_LEN; i++) {
     87    uint8_t prev = nonce[i];
     88    if (++nonce[i] > prev) {
     89      break;
     90    }
     91  }
     92  memcpy(challenge + HS_POW_NONCE_OFFSET, nonce, HS_POW_NONCE_LEN);
     93 }
     94 
     95 /* Helper: Build EquiX challenge (P || ID || C || N || INT_32(E)) and return
     96 * a newly allocated buffer containing it. */
     97 static uint8_t *
     98 build_equix_challenge(const ed25519_public_key_t *blinded_id,
     99                      const uint8_t *seed, const uint8_t *nonce,
    100                      const uint32_t effort)
    101 {
    102  size_t offset = 0;
    103  uint8_t *challenge = tor_malloc_zero(HS_POW_CHALLENGE_LEN);
    104 
    105  CTASSERT(HS_POW_ID_LEN == sizeof *blinded_id);
    106  tor_assert_nonfatal(!ed25519_public_key_is_zero(blinded_id));
    107 
    108  log_debug(LD_REND,
    109            "Constructing EquiX challenge with "
    110            "blinded service id %s, effort: %d",
    111            safe_str_client(ed25519_fmt(blinded_id)),
    112            effort);
    113 
    114  memcpy(challenge + offset, HS_POW_PSTRING, HS_POW_PSTRING_LEN);
    115  offset += HS_POW_PSTRING_LEN;
    116  memcpy(challenge + offset, blinded_id, HS_POW_ID_LEN);
    117  offset += HS_POW_ID_LEN;
    118  memcpy(challenge + offset, seed, HS_POW_SEED_LEN);
    119  offset += HS_POW_SEED_LEN;
    120  tor_assert(HS_POW_NONCE_OFFSET == offset);
    121  memcpy(challenge + offset, nonce, HS_POW_NONCE_LEN);
    122  offset += HS_POW_NONCE_LEN;
    123  set_uint32(challenge + offset, tor_htonl(effort));
    124  offset += HS_POW_EFFORT_LEN;
    125  tor_assert(HS_POW_CHALLENGE_LEN == offset);
    126 
    127  return challenge;
    128 }
    129 
    130 /** Helper: Return true iff the given challenge and solution for the given
    131 * effort do validate as in: R * E <= UINT32_MAX. */
    132 static bool
    133 validate_equix_challenge(const uint8_t *challenge,
    134                         const uint8_t *solution_bytes,
    135                         const uint32_t effort)
    136 {
    137  /* Fail if R * E > UINT32_MAX. */
    138  uint8_t hash_result[HS_POW_HASH_LEN];
    139  blake2b_state b2_state;
    140 
    141  if (BUG(blake2b_init(&b2_state, HS_POW_HASH_LEN) < 0)) {
    142    return false;
    143  }
    144 
    145  /* Construct: blake2b(C || N || E || S) */
    146  blake2b_update(&b2_state, challenge, HS_POW_CHALLENGE_LEN);
    147  blake2b_update(&b2_state, solution_bytes, HS_POW_EQX_SOL_LEN);
    148  blake2b_final(&b2_state, hash_result, HS_POW_HASH_LEN);
    149 
    150  /* Scale to 64 bit so we can avoid 32 bit overflow. */
    151  uint64_t RE = tor_htonl(get_uint32(hash_result)) * (uint64_t) effort;
    152 
    153  return RE <= UINT32_MAX;
    154 }
    155 
    156 /** Helper: Convert equix_solution to a byte array in little-endian order */
    157 static void
    158 pack_equix_solution(const equix_solution *sol_in,
    159                    uint8_t *bytes_out)
    160 {
    161  for (unsigned i = 0; i < EQUIX_NUM_IDX; i++) {
    162    bytes_out[i*2+0] = (uint8_t)sol_in->idx[i];
    163    bytes_out[i*2+1] = (uint8_t)(sol_in->idx[i] >> 8);
    164  }
    165 }
    166 
    167 /** Helper: Build an equix_solution from its corresponding byte array. */
    168 static void
    169 unpack_equix_solution(const uint8_t *bytes_in,
    170                      equix_solution *sol_out)
    171 {
    172  for (unsigned i = 0; i < EQUIX_NUM_IDX; i++) {
    173    sol_out->idx[i] = (uint16_t)bytes_in[i*2+0] |
    174                      (uint16_t)bytes_in[i*2+1] << 8;
    175  }
    176 }
    177 
    178 /** Helper: Map the CompiledProofOfWorkHash configuration option to its
    179 * corresponding equix_ctx_flags bit. */
    180 static equix_ctx_flags
    181 hs_pow_equix_option_flags(int CompiledProofOfWorkHash)
    182 {
    183  if (CompiledProofOfWorkHash == 0) {
    184    return 0;
    185  } else if (CompiledProofOfWorkHash == 1) {
    186    return EQUIX_CTX_MUST_COMPILE;
    187  } else {
    188    tor_assert_nonfatal(CompiledProofOfWorkHash == -1);
    189    return EQUIX_CTX_TRY_COMPILE;
    190  }
    191 }
    192 
    193 /** Solve the EquiX/blake2b PoW scheme using the parameters in pow_params, and
    194 * store the solution in pow_solution_out. Returns 0 on success and -1
    195 * otherwise. Called by a client, from a cpuworker thread. */
    196 int
    197 hs_pow_solve(const hs_pow_solver_inputs_t *pow_inputs,
    198             hs_pow_solution_t *pow_solution_out)
    199 {
    200  int ret = -1;
    201  uint8_t nonce[HS_POW_NONCE_LEN];
    202  uint8_t *challenge = NULL;
    203  equix_ctx *ctx = NULL;
    204 
    205  tor_assert(pow_inputs);
    206  tor_assert(pow_solution_out);
    207  const uint32_t effort = pow_inputs->effort;
    208 
    209  /* Generate a random nonce N. */
    210  crypto_rand((char *)nonce, sizeof nonce);
    211 
    212  /* Build EquiX challenge string */
    213  challenge = build_equix_challenge(&pow_inputs->service_blinded_id,
    214                                    pow_inputs->seed, nonce, effort);
    215 
    216  /* This runs on a cpuworker, let's not access global get_options().
    217   * Instead, the particular options we need are captured in pow_inputs. */
    218  ctx = equix_alloc(EQUIX_CTX_SOLVE |
    219    hs_pow_equix_option_flags(pow_inputs->CompiledProofOfWorkHash));
    220  if (!ctx) {
    221    goto end;
    222  }
    223 
    224  uint8_t sol_bytes[HS_POW_EQX_SOL_LEN];
    225  monotime_t start_time;
    226  monotime_get(&start_time);
    227  log_info(LD_REND, "Solving proof of work (effort %u)", effort);
    228 
    229  for (;;) {
    230    /* Calculate solutions to S = equix_solve(C || N || E),  */
    231    equix_solutions_buffer buffer;
    232    equix_result result;
    233    result = equix_solve(ctx, challenge, HS_POW_CHALLENGE_LEN, &buffer);
    234    switch (result) {
    235 
    236      case EQUIX_OK:
    237        for (unsigned i = 0; i < buffer.count; i++) {
    238          pack_equix_solution(&buffer.sols[i], sol_bytes);
    239 
    240          /* Check an Equi-X solution against the effort threshold */
    241          if (validate_equix_challenge(challenge, sol_bytes, effort)) {
    242            /* Store the nonce N. */
    243            memcpy(pow_solution_out->nonce, nonce, HS_POW_NONCE_LEN);
    244            /* Store the effort E. */
    245            pow_solution_out->effort = effort;
    246            /* We only store the first 4 bytes of the seed C. */
    247            memcpy(pow_solution_out->seed_head, pow_inputs->seed,
    248               sizeof(pow_solution_out->seed_head));
    249            /* Store the solution S */
    250            memcpy(&pow_solution_out->equix_solution,
    251                   sol_bytes, sizeof sol_bytes);
    252 
    253            monotime_t end_time;
    254            monotime_get(&end_time);
    255            int64_t duration_usec = monotime_diff_usec(&start_time, &end_time);
    256            log_info(LD_REND, "Proof of work solution (effort %u) found "
    257                     "using %s implementation in %u.%06u seconds",
    258                     effort,
    259                    (EQUIX_SOLVER_DID_USE_COMPILER & buffer.flags)
    260                       ? "compiled" : "interpreted",
    261                    (unsigned)(duration_usec / 1000000),
    262                    (unsigned)(duration_usec % 1000000));
    263 
    264            /* Indicate success and we are done. */
    265            ret = 0;
    266            goto end;
    267          }
    268        }
    269        break;
    270 
    271      case EQUIX_FAIL_CHALLENGE:
    272        /* This happens occasionally due to HashX rejecting some program
    273         * configurations. For our purposes here it's the same as count==0.
    274         * Increment the nonce and try again. */
    275        break;
    276 
    277      case EQUIX_FAIL_COMPILE:
    278        /* The interpreter is disabled and the compiler failed */
    279        log_warn(LD_REND, "Proof of work solver failed, "
    280                 "compile error with no fallback enabled.");
    281        goto end;
    282 
    283      /* These failures are not applicable to equix_solve, but included for
    284       * completeness and to satisfy exhaustive enum warnings. */
    285      case EQUIX_FAIL_ORDER:
    286      case EQUIX_FAIL_PARTIAL_SUM:
    287      case EQUIX_FAIL_FINAL_SUM:
    288      /* And these really should not happen, and indicate
    289       * programming errors if they do. */
    290      case EQUIX_FAIL_NO_SOLVER:
    291      case EQUIX_FAIL_INTERNAL:
    292      default:
    293        tor_assert_nonfatal_unreached();
    294        goto end;
    295    }
    296 
    297    /* No solutions for this nonce and/or none that passed the effort
    298     * threshold, increment and try again. */
    299    increment_and_set_nonce(nonce, challenge);
    300  }
    301 
    302 end:
    303  tor_free(challenge);
    304  equix_free(ctx);
    305  return ret;
    306 }
    307 
    308 /** Verify the solution in pow_solution using the service's current PoW
    309 * parameters found in pow_state. Returns 0 on success and -1 otherwise. Called
    310 * by the service. */
    311 int
    312 hs_pow_verify(const ed25519_public_key_t *service_blinded_id,
    313              const hs_pow_service_state_t *pow_state,
    314              const hs_pow_solution_t *pow_solution)
    315 {
    316  int ret = -1;
    317  uint8_t *challenge = NULL;
    318  nonce_cache_entry_t search, *entry = NULL;
    319  equix_ctx *ctx = NULL;
    320  const uint8_t *seed = NULL;
    321 
    322  tor_assert(pow_state);
    323  tor_assert(pow_solution);
    324  tor_assert(service_blinded_id);
    325  tor_assert_nonfatal(!ed25519_public_key_is_zero(service_blinded_id));
    326 
    327  /* Find a valid seed C that starts with the seed head. Fail if no such seed
    328   * exists. */
    329  if (fast_memeq(pow_state->seed_current, pow_solution->seed_head,
    330                 HS_POW_SEED_HEAD_LEN)) {
    331    seed = pow_state->seed_current;
    332  } else if (fast_memeq(pow_state->seed_previous, pow_solution->seed_head,
    333                        HS_POW_SEED_HEAD_LEN)) {
    334    seed = pow_state->seed_previous;
    335  } else {
    336    log_warn(LD_REND, "Seed head didn't match either seed.");
    337    goto done;
    338  }
    339 
    340  /* Fail if N = POW_NONCE is present in the replay cache. */
    341  memcpy(search.bytes.nonce, pow_solution->nonce, HS_POW_NONCE_LEN);
    342  memcpy(search.bytes.seed_head, pow_solution->seed_head,
    343         HS_POW_SEED_HEAD_LEN);
    344  entry = HT_FIND(nonce_cache_table_ht, &nonce_cache_table, &search);
    345  if (entry) {
    346    log_warn(LD_REND, "Found (nonce, seed) tuple in the replay cache.");
    347    goto done;
    348  }
    349 
    350  /* Build the challenge with the params we have. */
    351  challenge = build_equix_challenge(service_blinded_id, seed,
    352                                    pow_solution->nonce, pow_solution->effort);
    353 
    354  if (!validate_equix_challenge(challenge, pow_solution->equix_solution,
    355                                pow_solution->effort)) {
    356    log_warn(LD_REND, "Verification of challenge effort in PoW failed.");
    357    goto done;
    358  }
    359 
    360  ctx = equix_alloc(EQUIX_CTX_VERIFY |
    361    hs_pow_equix_option_flags(get_options()->CompiledProofOfWorkHash));
    362  if (!ctx) {
    363    goto done;
    364  }
    365 
    366  /* Fail if equix_verify() != EQUIX_OK */
    367  equix_solution equix_sol;
    368  unpack_equix_solution(pow_solution->equix_solution, &equix_sol);
    369  equix_result result = equix_verify(ctx, challenge, HS_POW_CHALLENGE_LEN,
    370                                     &equix_sol);
    371  if (result != EQUIX_OK) {
    372    log_warn(LD_REND, "Verification of EquiX solution in PoW failed.");
    373    goto done;
    374  }
    375 
    376  /* PoW verified successfully. */
    377  ret = 0;
    378 
    379  /* Add the (nonce, seed) tuple to the replay cache. */
    380  entry = tor_malloc_zero(sizeof(nonce_cache_entry_t));
    381  memcpy(entry->bytes.nonce, pow_solution->nonce, HS_POW_NONCE_LEN);
    382  memcpy(entry->bytes.seed_head, pow_solution->seed_head,
    383         HS_POW_SEED_HEAD_LEN);
    384  HT_INSERT(nonce_cache_table_ht, &nonce_cache_table, entry);
    385 
    386 done:
    387  tor_free(challenge);
    388  equix_free(ctx);
    389  return ret;
    390 }
    391 
    392 /** Remove entries from the (nonce, seed) replay cache which are for the seed
    393 * beginning with seed_head. If seed_head is NULL, remove all cache entries. */
    394 void
    395 hs_pow_remove_seed_from_cache(const uint8_t *seed_head)
    396 {
    397  /* If nonce_cache_entry_has_seed returns 1, the entry is removed. */
    398  HT_FOREACH_FN(nonce_cache_table_ht, &nonce_cache_table,
    399                nonce_cache_entry_match_seed_and_free, (void*)seed_head);
    400 }
    401 
    402 /** Free a given PoW service state. */
    403 void
    404 hs_pow_free_service_state(hs_pow_service_state_t *state)
    405 {
    406  if (state == NULL) {
    407    return;
    408  }
    409  rend_pqueue_clear(state);
    410  tor_assert(smartlist_len(state->rend_request_pqueue) == 0);
    411  smartlist_free(state->rend_request_pqueue);
    412  mainloop_event_free(state->pop_pqueue_ev);
    413  tor_free(state);
    414 }
    415 
    416 /* =====
    417   Thread workers
    418   =====*/
    419 
    420 /**
    421 * An object passed to a worker thread that will try to solve the pow.
    422 */
    423 typedef struct pow_worker_job_t {
    424 
    425  /** Inputs for the PoW solver (seed, chosen effort) */
    426  hs_pow_solver_inputs_t pow_inputs;
    427 
    428  /** State: we'll look these up to figure out how to proceed after. */
    429  uint32_t intro_circ_identifier;
    430  uint8_t rend_circ_cookie[HS_REND_COOKIE_LEN];
    431 
    432  /** Output: The worker thread will malloc and write its answer here,
    433   * or set it to NULL if it produced no useful answer. */
    434  hs_pow_solution_t *pow_solution_out;
    435 
    436 } pow_worker_job_t;
    437 
    438 /**
    439 * Worker function. This function runs inside a worker thread and receives
    440 * a pow_worker_job_t as its input.
    441 */
    442 static workqueue_reply_t
    443 pow_worker_threadfn(void *state_, void *work_)
    444 {
    445  (void)state_;
    446  pow_worker_job_t *job = work_;
    447  job->pow_solution_out = tor_malloc_zero(sizeof(hs_pow_solution_t));
    448 
    449  if (hs_pow_solve(&job->pow_inputs, job->pow_solution_out)) {
    450    tor_free(job->pow_solution_out);
    451    job->pow_solution_out = NULL; /* how we signal that we came up empty */
    452  }
    453  return WQ_RPL_REPLY;
    454 }
    455 
    456 /**
    457 * Helper: release all storage held in <b>job</b>.
    458 */
    459 static void
    460 pow_worker_job_free(pow_worker_job_t *job)
    461 {
    462  if (!job)
    463    return;
    464  tor_free(job->pow_solution_out);
    465  tor_free(job);
    466 }
    467 
    468 /**
    469 * Worker function: This function runs in the main thread, and receives
    470 * a pow_worker_job_t that the worker thread has already processed.
    471 */
    472 static void
    473 pow_worker_replyfn(void *work_)
    474 {
    475  tor_assert(in_main_thread());
    476  tor_assert(work_);
    477 
    478  pow_worker_job_t *job = work_;
    479 
    480  /* Look up the circuits that we're going to use this pow in.
    481   * There's room for improvement here. We already had a fast mapping to
    482   * rend circuits from some kind of identifier that we can keep in a
    483   * pow_worker_job_t, but we don't have that index for intro circs at this
    484   * time. If the linear search in circuit_get_by_global_id() is ever a
    485   * noticeable bottleneck we should add another map.
    486   */
    487  origin_circuit_t *intro_circ =
    488    circuit_get_by_global_id(job->intro_circ_identifier);
    489  origin_circuit_t *rend_circ =
    490    hs_circuitmap_get_established_rend_circ_client_side(job->rend_circ_cookie);
    491 
    492  /* try to re-create desc and ip */
    493  const ed25519_public_key_t *service_identity_pk = NULL;
    494  const hs_descriptor_t *desc = NULL;
    495  const hs_desc_intro_point_t *ip = NULL;
    496  if (intro_circ)
    497    service_identity_pk = &intro_circ->hs_ident->identity_pk;
    498  if (service_identity_pk)
    499    desc = hs_cache_lookup_as_client(service_identity_pk);
    500  if (desc)
    501    ip = find_desc_intro_point_by_ident(intro_circ->hs_ident, desc);
    502 
    503  if (intro_circ && rend_circ && service_identity_pk && desc && ip &&
    504      job->pow_solution_out) {
    505 
    506    /* successful pow solve, and circs still here */
    507    log_info(LD_REND, "Got a PoW solution we like! Shipping it!");
    508 
    509    /* Set flag to reflect that the HS we are attempting to rendezvous has PoW
    510     * defenses enabled, and as such we will need to be more lenient with
    511     * timing out while waiting for the service-side circuit to be built. */
    512    rend_circ->hs_with_pow_circ = 1;
    513 
    514    /* Remember the PoW effort we chose, for client-side rend circuits. */
    515    rend_circ->hs_pow_effort = job->pow_inputs.effort;
    516 
    517    // and then send that intro cell
    518    if (send_introduce1(intro_circ, rend_circ,
    519                        desc, job->pow_solution_out, ip) < 0) {
    520      /* if it failed, mark the intro point as ready to start over */
    521      intro_circ->hs_currently_solving_pow = 0;
    522    }
    523 
    524  } else {
    525    if (!job->pow_solution_out) {
    526      log_warn(LD_REND, "PoW cpuworker returned with no solution");
    527    } else {
    528      log_info(LD_REND, "PoW solution completed but we can "
    529                        "no longer locate its circuit");
    530    }
    531    if (intro_circ) {
    532      intro_circ->hs_currently_solving_pow = 0;
    533    }
    534  }
    535 
    536  pow_worker_job_free(job);
    537 }
    538 
    539 /**
    540 * Queue the job of solving the pow in a worker thread.
    541 */
    542 int
    543 hs_pow_queue_work(uint32_t intro_circ_identifier,
    544                  const uint8_t *rend_circ_cookie,
    545                  const hs_pow_solver_inputs_t *pow_inputs)
    546 {
    547  tor_assert(in_main_thread());
    548  tor_assert(rend_circ_cookie);
    549  tor_assert(pow_inputs);
    550  tor_assert_nonfatal(
    551    !ed25519_public_key_is_zero(&pow_inputs->service_blinded_id));
    552 
    553  pow_worker_job_t *job = tor_malloc_zero(sizeof(*job));
    554  job->intro_circ_identifier = intro_circ_identifier;
    555  memcpy(&job->rend_circ_cookie, rend_circ_cookie,
    556         sizeof job->rend_circ_cookie);
    557  memcpy(&job->pow_inputs, pow_inputs, sizeof job->pow_inputs);
    558 
    559  workqueue_entry_t *work;
    560  work = cpuworker_queue_work(WQ_PRI_LOW,
    561                              pow_worker_threadfn,
    562                              pow_worker_replyfn,
    563                              job);
    564  if (!work) {
    565    pow_worker_job_free(job);
    566    return -1;
    567  }
    568  return 0;
    569 }