Summary
A private report identified two issues in randomx-rs, the Rust binding to RandomX that zecnerod uses to verify proof of work, and two issues in the XMRig-based miner. None of them changes RandomX hash output. The binding fixes ship in zecnerod testnet-v0.2.0 (the NU1 release, Sunday, October 4, 2026). The miner fixes have been public in xmrig-zecnero since September 27, 2026.
Severity: High (latent, no known trigger), for the randomx-rs hash output buffer (issue 1). The other three issues are Low-Medium (2 and 4) and Low (3).
Consensus and chain impact: none. Hash output is unchanged; no fork, no chain reorganisation and no action on already-mined blocks is needed.
| # | Issue | Component | Severity |
|---|---|---|---|
| 1 | Hash output written through a pointer derived from an immutable array | randomx-rs-zecnero | High (latent, no known trigger) |
| 2 | Result of randomx_create_vm not checked for NULL | randomx-rs-zecnero | Low-Medium |
| 3 | Dataset queue (RxQueue) state read without its lock | xmrig-zecnero | Low |
| 4 | RandomX VM creation failure not handled | xmrig-zecnero | Low-Medium |
Affected components and versions
| Component | Affected | Fixed |
|---|---|---|
| randomx-rs-zecnero (Rust RandomX binding) | All revisions before the fix, including fbcea21 | Commit 1cd3c9d, merged in 2065ff09 |
| zecnerod (node) | Every build that pins randomx-rs-zecnero before the fix, including the ones that pin fbcea21: testnet-v0.1.x and the testnet-v0.2.0 release candidates built before the update | testnet-v0.2.0 (October 4, 2026); main from 3c0a16c4 |
| xmrig-zecnero (miner) | master before d5878de4 and 9f15a383, including the xmrig-zecnero binary shipped with testnet-v0.1.1 and testnet-v0.1.2 | master from 9f15a383 (public since September 27, 2026); testnet-v0.2.0 (its miner is built from master at or after 9f15a383) |
The same code exists upstream, in tari-project/randomx-rs (v1.6.0) and xmrig/xmrig (v6.26.0). Both upstream projects were notified on September 29, 2026; see Upstream coordination.
Details
1. randomx-rs: hash output written through a pointer derived from an immutable array
Severity: High (latent, no known trigger).
RandomXVM::calculate_hash and RandomXVM::calculate_hash_set created the 32-byte output buffer as an immutable local array and passed the C library a *mut pointer cast from as_ptr(). The C library then wrote the hash through that pointer. Writing through a pointer derived from a shared borrow is undefined behaviour in Rust (Miri reports it as a write through a SharedReadOnly tag).
Impact: latent. The compiler is allowed to assume the array still holds zeros after the call, in which case the binding could return an error ("calculated hash was empty") or an all-zero hash instead of the real one. We did not observe a miscompile with rustc 1.98.1 in debug or release builds, and the zecnerod proof-of-work test vectors pass with and without the fix; but nothing prevents a future compiler version from producing one. It is rated High because proof-of-work verification depends on this output; there is no known trigger with any current compiler.
Fix: the output buffer is a mutable array and the pointer comes from as_mut_ptr(), in the single-hash, batch and raw FFI test paths; input pointers are passed as *const. A regression test checks the hash against the rx/zecnero test vectors (light, batch and fast mode) and that it is never all zeros.
2. randomx-rs: result of randomx_create_vm not checked
Severity: Low-Medium.
RandomXVM::new wrapped the pointer returned by randomx_create_vm in Ok(..) without checking it for NULL. The C library returns NULL when it cannot allocate the VM (for example when large pages or executable memory for the JIT are unavailable). The next hash call then dereferenced NULL inside the C library.
Impact: a node could crash (segmentation fault) instead of reporting an error when VM allocation fails, for example under memory pressure. This is a local robustness issue; it cannot be triggered remotely by a peer, and it does not affect which blocks are accepted.
Fix: RandomXVM::new returns CreationError when the VM cannot be created, and Drop skips a null VM. A test hook forces the failure path in tests.
3. xmrig-zecnero: dataset queue state read without its lock
Severity: Low.
The background thread of the RandomX dataset queue (RxQueue) read the queue state outside its mutex, and the readiness check touched the dataset storage before checking the state, while the background thread could be re-creating the dataset.
Impact: a data race, reported by ThreadSanitizer during re-seeding and shutdown: a caller could read the dataset and cache pointers while the background thread was re-creating them. Possible effects are a miner crash or invalid (rejected) shares. It does not affect the node or the chain.
Fix: d5878de4 "Read queue state under the lock".
4. xmrig-zecnero: RandomX VM creation failure not handled
Severity: Low-Medium.
The CPU worker and the result verification used the RandomX VM without checking that it had been created.
Impact: the miner crashed instead of recovering when VM allocation failed.
Fix: 9f15a383 "Handle RandomX VM creation failure": VM creation is retried a bounded number of times; a worker without a VM skips hashing and retries on the next job; result verification counts the affected nonces as errors.
These two miner fixes were published earlier than planned, on September 27, 2026, before the coordinated disclosure date. They have been public on xmrig-zecnero master since then.
Consensus and hash output
None of the fixes changes the RandomX algorithm, its parameters or its output. Before and after the fix, the binding produces the same hashes for the rx/zecnero test vectors, and zecnerod's proof-of-work tests (block header vectors) pass unchanged. Nodes running the fixed and unfixed binding agree on every block: there is no fork and no chain impact, and no action is needed for blocks already mined.
What to do
- Node operators: upgrade to zecnerod testnet-v0.2.0 (released October 4, 2026). The NU1 upgrade in that release is required anyway; the binding fixes come with it.
- Miners: use an xmrig-zecnero build from master at or after
9f15a383, or the miner shipped with testnet-v0.2.0. The mining guide installs the current one. - Other users of randomx-rs (upstream or forks): apply the equivalent change (mutable output buffer, NULL check after
randomx_create_vm), as in our fix1cd3c9d. No upstream fix was public on October 4, 2026.
Upstream coordination
- Tari (tari-project/randomx-rs) was notified privately on September 29, 2026. No upstream issue, advisory or fix was public on October 4, 2026; our fix is
1cd3c9d. - XMRig (xmrig/xmrig) was notified privately on September 29, 2026, with a note that our fork's fixes were already public. No upstream issue or fix was public on October 4, 2026; our fixes are
d5878de4and9f15a383.
Credit
Reported by an independent security researcher.
Timeline (UTC)
- 2026-09-26: issues reported privately to the Zecnero project.
- 2026-09-27: issues confirmed (Miri, ThreadSanitizer, failure-injection tests); fixes prepared. The xmrig-zecnero fixes (
d5878de4,9f15a383) were published earlier than planned. - 2026-09-29: Tari, XMRig and the reporter notified; public date set to 2026-10-02.
- 2026-10-01: Planned for 2026-10-05; published 2026-10-04 after no response from the upstream maintainers, who were notified on 2026-10-03 and 2026-10-04.
- 2026-10-04: public date brought forward to 2026-10-04, with the release.
- 2026-10-04: fixes for randomx-rs-zecnero and zecnerod published; this advisory.
- 2026-10-04: zecnerod testnet-v0.2.0 (NU1) released with the binding fixes.
Reporting security issues
Report vulnerabilities privately, before posting anywhere public. Encrypt the report to the Zecnero key if you can: it is the key that signs every release, zecnero.asc, fingerprint FFFC 8AC0 2BF7 B4B4 50B4 3B1F 6532 09A3 44E7 5767. How to check the fingerprint is on the contact page.