From a397a92be2032e781479fa4d53a04f9b369ea1ac Mon Sep 17 00:00:00 2001 From: Micah Elizabeth Scott Date: Sun, 28 May 2023 16:35:31 -0700 Subject: [PATCH] hs_pow: Update for equix API to fix issue 40794 This change adapts the hs_pow layer and unit tests to API changes in hashx and equix which modify the fault recovery responsibilities and reporting behaivor. This and the corresponding implementation changes in hashx and equix form the fix for #40794, both solving the segfault and giving hashx a way to report those failures up the call chain without them being mistaken for a different error (unusable seed) that would warrant a retry. To handle these new late compiler failures with a minimum of fuss or inefficiency, the failover is delegated to the internals of hashx and tor needs only pass in a EQUIX_CTX_TRY_COMPILE flag to get the behavior that tor was previously responsible for implementing. Signed-off-by: Micah Elizabeth Scott --- src/feature/hs/hs_pow.c | 108 ++++++++++++++++++++++-------------- src/test/test_crypto.c | 10 ++-- src/test/test_crypto_slow.c | 63 ++++++++++----------- src/test/test_sandbox.c | 27 ++++----- 4 files changed, 115 insertions(+), 93 deletions(-) diff --git a/src/feature/hs/hs_pow.c b/src/feature/hs/hs_pow.c index f75b3cb119..27d09cb0b4 100644 --- a/src/feature/hs/hs_pow.c +++ b/src/feature/hs/hs_pow.c @@ -27,6 +27,7 @@ #include "lib/cc/ctassert.h" #include "core/mainloop/cpuworker.h" #include "lib/evloop/workqueue.h" +#include "lib/time/compat_time.h" /** Replay cache set up */ /** Cache entry for (nonce, seed) replay protection. */ @@ -91,23 +92,6 @@ increment_and_set_nonce(uint8_t *nonce, uint8_t *challenge) memcpy(challenge + HS_POW_NONCE_OFFSET, nonce, HS_POW_NONCE_LEN); } -/* Helper: Allocate an EquiX context, using the much faster compiled - * implementation of hashx if it's available on this architecture. */ -static equix_ctx * -build_equix_ctx(equix_ctx_flags flags) -{ - equix_ctx *ctx = equix_alloc(flags | EQUIX_CTX_COMPILE); - if (ctx == EQUIX_NOTSUPP) { - ctx = equix_alloc(flags); - } - tor_assert_nonfatal(ctx != EQUIX_NOTSUPP); - tor_assert_nonfatal(ctx != NULL); - if (ctx == EQUIX_NOTSUPP) { - ctx = NULL; - } - return ctx; -} - /* Helper: Build EquiX challenge (P || ID || C || N || INT_32(E)) and return * a newly allocated buffer containing it. */ static uint8_t * @@ -214,36 +198,82 @@ hs_pow_solve(const hs_pow_solver_inputs_t *pow_inputs, challenge = build_equix_challenge(&pow_inputs->service_blinded_id, pow_inputs->seed, nonce, effort); - ctx = build_equix_ctx(EQUIX_CTX_SOLVE); + ctx = equix_alloc(EQUIX_CTX_SOLVE | EQUIX_CTX_TRY_COMPILE); if (!ctx) { goto end; } - equix_solution solutions[EQUIX_MAX_SOLS]; - uint8_t sol_bytes[HS_POW_EQX_SOL_LEN]; + uint8_t sol_bytes[HS_POW_EQX_SOL_LEN]; + monotime_t start_time; + monotime_get(&start_time); log_info(LD_REND, "Solving proof of work (effort %u)", effort); + for (;;) { /* Calculate solutions to S = equix_solve(C || N || E), */ - int count = equix_solve(ctx, challenge, HS_POW_CHALLENGE_LEN, solutions); - for (int i = 0; i < count; i++) { - pack_equix_solution(&solutions[i], sol_bytes); + equix_solutions_buffer buffer; + equix_result result; + result = equix_solve(ctx, challenge, HS_POW_CHALLENGE_LEN, &buffer); + switch (result) { - /* Check an Equi-X solution against the effort threshold */ - if (validate_equix_challenge(challenge, sol_bytes, effort)) { - /* Store the nonce N. */ - memcpy(pow_solution_out->nonce, nonce, HS_POW_NONCE_LEN); - /* Store the effort E. */ - pow_solution_out->effort = effort; - /* We only store the first 4 bytes of the seed C. */ - memcpy(pow_solution_out->seed_head, pow_inputs->seed, + case EQUIX_OK: + for (unsigned i = 0; i < buffer.count; i++) { + pack_equix_solution(&buffer.sols[i], sol_bytes); + + /* Check an Equi-X solution against the effort threshold */ + if (validate_equix_challenge(challenge, sol_bytes, effort)) { + /* Store the nonce N. */ + memcpy(pow_solution_out->nonce, nonce, HS_POW_NONCE_LEN); + /* Store the effort E. */ + pow_solution_out->effort = effort; + /* We only store the first 4 bytes of the seed C. */ + memcpy(pow_solution_out->seed_head, pow_inputs->seed, sizeof(pow_solution_out->seed_head)); - /* Store the solution S */ - memcpy(&pow_solution_out->equix_solution, sol_bytes, sizeof sol_bytes); + /* Store the solution S */ + memcpy(&pow_solution_out->equix_solution, + sol_bytes, sizeof sol_bytes); - /* Indicate success and we are done. */ - ret = 0; + monotime_t end_time; + monotime_get(&end_time); + int64_t duration_usec = monotime_diff_usec(&start_time, &end_time); + log_info(LD_REND, "Proof of work solution (effort %u) found " + "using %s implementation in %u.%06u seconds", + effort, + (EQUIX_SOLVER_DID_USE_COMPILER & buffer.flags) + ? "compiled" : "interpreted", + (unsigned)(duration_usec / 1000000), + (unsigned)(duration_usec % 1000000)); + + /* Indicate success and we are done. */ + ret = 0; + goto end; + } + } + break; + + case EQUIX_FAIL_CHALLENGE: + /* This happens occasionally due to HashX rejecting some program + * configurations. For our purposes here it's the same as count==0. + * Increment the nonce and try again. */ + break; + + case EQUIX_FAIL_COMPILE: + /* The interpreter is disabled and the compiler failed */ + log_warn(LD_REND, "Proof of work solver failed, " + "compile error with no fallback enabled."); + goto end; + + /* These failures are not applicable to equix_solve, but included for + * completeness and to satisfy exhaustive enum warnings. */ + case EQUIX_FAIL_ORDER: + case EQUIX_FAIL_PARTIAL_SUM: + case EQUIX_FAIL_FINAL_SUM: + /* And these really should not happen, and indicate + * programming errors if they do. */ + case EQUIX_FAIL_NO_SOLVER: + case EQUIX_FAIL_INTERNAL: + default: + tor_assert_nonfatal_unreached(); goto end; - } } /* No solutions for this nonce and/or none that passed the effort @@ -309,7 +339,7 @@ hs_pow_verify(const ed25519_public_key_t *service_blinded_id, goto done; } - ctx = build_equix_ctx(EQUIX_CTX_VERIFY); + ctx = equix_alloc(EQUIX_CTX_VERIFY | EQUIX_CTX_TRY_COMPILE); if (!ctx) { goto done; } @@ -401,11 +431,7 @@ pow_worker_threadfn(void *state_, void *work_) log_warn(LD_REND, "Failed to run the proof of work solver"); tor_free(job->pow_solution_out); job->pow_solution_out = NULL; /* how we signal that we came up empty */ - return WQ_RPL_REPLY; } - - /* we have a winner! */ - log_info(LD_REND, "cpuworker has a proof of work solution"); return WQ_RPL_REPLY; } diff --git a/src/test/test_crypto.c b/src/test/test_crypto.c index 926d4178c1..db5a2db650 100644 --- a/src/test/test_crypto.c +++ b/src/test/test_crypto.c @@ -2982,9 +2982,9 @@ test_crypto_hashx(void *arg) static const struct { hashx_type type; } variations[] = { - { HASHX_INTERPRETED }, + { HASHX_TYPE_INTERPRETED }, #if defined(_M_X64) || defined(__x86_64__) || defined(__aarch64__) - { HASHX_COMPILED }, + { HASHX_TYPE_COMPILED }, #endif }; @@ -3013,12 +3013,12 @@ test_crypto_hashx(void *arg) ctx = hashx_alloc(variations[vari_i].type); tt_ptr_op(ctx, OP_NE, NULL); - tt_ptr_op(ctx, OP_NE, HASHX_NOTSUPP); retval = hashx_make(ctx, seed_literal, seed_len); - tt_int_op(retval, OP_EQ, 1); + tt_int_op(retval, OP_EQ, HASHX_OK); memset(out_actual, 0xa5, sizeof out_actual); - hashx_exec(ctx, hash_input, out_actual); + retval = hashx_exec(ctx, hash_input, out_actual); + tt_int_op(retval, OP_EQ, HASHX_OK); tt_mem_op(out_actual, OP_EQ, out_expected, sizeof out_actual); } } diff --git a/src/test/test_crypto_slow.c b/src/test/test_crypto_slow.c index 5ffd199813..23bc7a852f 100644 --- a/src/test/test_crypto_slow.c +++ b/src/test/test_crypto_slow.c @@ -636,14 +636,21 @@ test_crypto_equix(void *arg) static const struct { equix_ctx_flags flags; equix_result expected; + equix_solution_flags sol_flags; } variations[] = { - {0, EQUIX_OK}, - {0, EQUIX_ORDER}, - {0, EQUIX_PARTIAL_SUM}, + {0, EQUIX_OK, 0}, + {0, EQUIX_FAIL_ORDER, 0}, + {0, EQUIX_FAIL_PARTIAL_SUM, 0}, #if defined(_M_X64) || defined(__x86_64__) || defined(__aarch64__) - {EQUIX_CTX_COMPILE, EQUIX_OK}, - {EQUIX_CTX_COMPILE, EQUIX_ORDER}, - {EQUIX_CTX_COMPILE, EQUIX_PARTIAL_SUM}, + { EQUIX_CTX_MUST_COMPILE, EQUIX_OK, + EQUIX_SOLVER_DID_USE_COMPILER + }, + { EQUIX_CTX_MUST_COMPILE, EQUIX_FAIL_ORDER, + EQUIX_SOLVER_DID_USE_COMPILER + }, + { EQUIX_CTX_MUST_COMPILE, EQUIX_FAIL_PARTIAL_SUM, + EQUIX_SOLVER_DID_USE_COMPILER + }, #endif }; @@ -659,48 +666,42 @@ test_crypto_equix(void *arg) for (unsigned vari_i = 0; vari_i < num_variations; vari_i++) { const equix_ctx_flags flags = variations[vari_i].flags; + const equix_solution_flags sol_flags = variations[vari_i].sol_flags; const equix_result expected = variations[vari_i].expected; - equix_solution sols_actual[EQUIX_MAX_SOLS]; + equix_solutions_buffer output; equix_ctx *solve_ctx = NULL, *verify_ctx = NULL; solve_ctx = equix_alloc(EQUIX_CTX_SOLVE | flags); tt_ptr_op(solve_ctx, OP_NE, NULL); - tt_ptr_op(solve_ctx, OP_NE, EQUIX_NOTSUPP); /* Solve phase: Make sure the test vector matches */ - memset(sols_actual, 0xa5, sizeof sols_actual); - int retval = equix_solve(solve_ctx, challenge_literal, - challenge_len, sols_actual); - tt_int_op(retval, OP_EQ, num_sols); - tt_mem_op(sols_actual, OP_EQ, sols_expected, + memset(&output, 0xa5, sizeof output); + equix_result result; + result = equix_solve(solve_ctx, challenge_literal, + challenge_len, &output); + tt_int_op(result, OP_EQ, EQUIX_OK); + tt_int_op(output.count, OP_EQ, num_sols); + tt_int_op(output.flags, OP_EQ, sol_flags); + tt_mem_op(output.sols, OP_EQ, sols_expected, num_sols * sizeof(equix_solution)); verify_ctx = equix_alloc(EQUIX_CTX_VERIFY | flags); tt_ptr_op(verify_ctx, OP_NE, NULL); - tt_ptr_op(verify_ctx, OP_NE, EQUIX_NOTSUPP); /* Use each solution for positive and negative tests of verify */ for (size_t sol_i = 0; sol_i < num_sols; sol_i++) { - equix_result result; equix_idx tmp_idx; - equix_solution *sol = &sols_actual[sol_i]; + equix_solution *sol = &output.sols[sol_i]; - switch (expected) { - case EQUIX_ORDER: - /* Swap two otherwise valid indices, to trigger an order error */ - tmp_idx = sol->idx[0]; - sol->idx[0] = sol->idx[1]; - sol->idx[1] = tmp_idx; - break; - case EQUIX_FINAL_SUM: - case EQUIX_PARTIAL_SUM: - /* Most changes to the solution will cause a partial sum error */ - sol->idx[0]++; - break; - case EQUIX_OK: - case EQUIX_CHALLENGE: - break; + if (expected == EQUIX_FAIL_ORDER) { + /* Swap two otherwise valid indices, to trigger an order error */ + tmp_idx = sol->idx[0]; + sol->idx[0] = sol->idx[1]; + sol->idx[1] = tmp_idx; + } else if (expected == EQUIX_FAIL_PARTIAL_SUM) { + /* Most changes to the solution will cause a partial sum error */ + sol->idx[0]++; } result = equix_verify(verify_ctx, challenge_literal, diff --git a/src/test/test_sandbox.c b/src/test/test_sandbox.c index a28c9b6e41..64182ecc91 100644 --- a/src/test/test_sandbox.c +++ b/src/test/test_sandbox.c @@ -315,32 +315,27 @@ test_sandbox_crypto_equix(void *arg) {{ 0x62c5, 0x86d1, 0x5752, 0xe1f0, 0x12da, 0x8f33, 0x7336, 0xf161 }}, }; - equix_solution sols_actual[EQUIX_MAX_SOLS] = { 0 }; + equix_solutions_buffer output; equix_ctx *solve_ctx = NULL, *verify_ctx = NULL; - /* TODO: A subsequent change will modify these flags to use an auto fallback - * that will be built into our fork of equix. (This implements a - * performant and low-complexity way to share the generated program - * state during fallback instead of re-generating it.) - */ - solve_ctx = equix_alloc(EQUIX_CTX_SOLVE | EQUIX_CTX_COMPILE); + solve_ctx = equix_alloc(EQUIX_CTX_SOLVE | EQUIX_CTX_TRY_COMPILE); tt_ptr_op(solve_ctx, OP_NE, NULL); - tt_ptr_op(solve_ctx, OP_NE, EQUIX_NOTSUPP); - int retval = equix_solve(solve_ctx, challenge_literal, - challenge_len, sols_actual); - tt_int_op(retval, OP_EQ, num_sols); - tt_mem_op(sols_actual, OP_EQ, sols_expected, + equix_result result; + memset(&output, 0xEE, sizeof output); + result = equix_solve(solve_ctx, challenge_literal, challenge_len, &output); + tt_int_op(result, OP_EQ, EQUIX_OK); + tt_int_op(output.count, OP_EQ, num_sols); + tt_int_op(output.flags, OP_EQ, 0); /* EQUIX_SOLVER_DID_USE_COMPILER unset */ + tt_mem_op(output.sols, OP_EQ, sols_expected, num_sols * sizeof(equix_solution)); - verify_ctx = equix_alloc(EQUIX_CTX_VERIFY | EQUIX_CTX_COMPILE); + verify_ctx = equix_alloc(EQUIX_CTX_VERIFY | EQUIX_CTX_TRY_COMPILE); tt_ptr_op(verify_ctx, OP_NE, NULL); - tt_ptr_op(verify_ctx, OP_NE, EQUIX_NOTSUPP); /* Test one of the solutions randomly */ - equix_result result; const unsigned sol_i = crypto_rand_int(num_sols); - equix_solution *sol = &sols_actual[sol_i]; + equix_solution *sol = &output.sols[sol_i]; result = equix_verify(verify_ctx, challenge_literal, challenge_len, sol);