Skip to content

Commit b420ee9

Browse files
committed
fix(test): the host KAT punned its result type, and LP64 made it matter
`run_grind` declared its result `uint64_t` and handed the address to the kernel as `volatile unsigned long long *`. Those are the same type on Darwin/arm64 and different types of the same width on LP64 glibc, so on Linux the cast type-punned; with `#include "rpx.cu"` putting the whole kernel in this translation unit, GCC 13.3 at -O2 was free under TBAA to assume a write through `unsigned long long *` could not touch an `unsigned long`, and to keep `result` in a register across the inlined call. It did. `run_grind` returned UINT64_MAX for every input, so layer 8's two checks per vector that expect the SENTINEL passed VACUOUSLY while the two that expect a found nonce failed. Six rows, at every sha back to the gated base c00342c, on a target that passes on a clang/arm64 laptop where the two types coincide. Measured on the box at this sha: -O2 6 FAILURE(S) -O2 -fno-strict-aliasing ALL HOST KAT CHECKS PASS -O0 ALL HOST KAT CHECKS PASS None of it was ever a statement about the device grind. This file is a HOST replay of the kernel source through `cuda_host_shim.h` — no nvcc, no cubin, no device — so the defect was in the harness holding the result, not in the kernel it tests. The production path re-validates every device nonce with the host predicate, and the block pins read `host fallbacks 0` throughout. `crypto/math-cuda/tests/host_kat/` holds exactly one instance of the pattern and this is it: the shim's `atomicMin` takes `unsigned long long *` as a parameter, and the kernels' casts there only drop `volatile` from an already-matching type. Test-only; no production code and no proof bytes move. It also unblocks this lineage's CI `host-kat` job, which has been failing since the device grind landed. (cherry picked from commit 90a38ec)
1 parent 05f0500 commit b420ee9

1 file changed

Lines changed: 25 additions & 1 deletion

File tree

‎crypto/math-cuda/tests/host_kat/rpx_host_kat.cpp‎

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1097,7 +1097,31 @@ void permute_probe_matches_the_oracle_table() {
10971097
// ---------------------------------------------------------------------------
10981098
uint64_t run_grind(const uint64_t inner[4], uint8_t factor, uint64_t base, uint64_t count) {
10991099
const uint64_t limit = (uint64_t)1 << (64 - factor);
1100-
uint64_t result = UINT64_MAX;
1100+
// ⛔⛔ `unsigned long long`, NOT `uint64_t`, AND THAT DIFFERENCE WAS THE BUG.
1101+
// The kernel takes `volatile unsigned long long *` because that is the type
1102+
// CUDA's `atomicMin` overload wants, so this object's address is handed out
1103+
// under it. `uint64_t` is `unsigned long long` on Darwin/arm64 and
1104+
// `unsigned long` on LP64 glibc — a DIFFERENT type of the same width — so on
1105+
// Linux the cast type-punned, and with `#include "rpx.cu"` putting the whole
1106+
// kernel in this translation unit, GCC 13.3 at -O2 was free under TBAA to
1107+
// assume a write through `unsigned long long *` could not touch an
1108+
// `unsigned long`, and to keep `result` in a register across the inlined call.
1109+
//
1110+
// It did. `run_grind` returned `UINT64_MAX` for every input on the box, so
1111+
// every check below that expects the SENTINEL passed vacuously while every
1112+
// check that expects a FOUND nonce failed — six rows, at every sha back to
1113+
// the gated base `c00342c1f`, while the same source passed on a clang/arm64
1114+
// laptop where the two types coincide. Measured on the box 2026-09-20:
1115+
// `-O2` → 6 failures; `-O2 -fno-strict-aliasing` → all pass; `-O0` → all pass.
1116+
//
1117+
// ⚠ NONE OF THAT WAS EVER A STATEMENT ABOUT THE DEVICE GRIND. This file is a
1118+
// HOST replay of the kernel source through `cuda_host_shim.h`; the defect
1119+
// was in the harness holding the result, not in the kernel it was testing.
1120+
// ⛔ Do not "tidy" this back to `uint64_t`: matching the pointer type the
1121+
// kernel is given is what makes the access well-defined, and the box gate
1122+
// carries a mutation that restores `uint64_t` and requires those six rows
1123+
// back — so the tidy-up would be caught, loudly, by a red nobody wants again.
1124+
unsigned long long result = UINT64_MAX;
11011125
CUDA_HOST_SINGLE_THREAD();
11021126
rpx_grind_search(inner, limit, base, count, (volatile unsigned long long *)&result);
11031127
return result;

0 commit comments

Comments
 (0)