cryptonote_basic: copy difficulty hash words
What changed, and why it matters
This commit changes how Monero reads 64-bit chunks from cryptographic hashes during difficulty checks. Previously the code cast a byte array directly to a uint64_t pointer, which can violate strict aliasing rules and cause unaligned or misread memory on some platforms. The new code copies the bytes with memcpy first, then converts endianness. This is a correctness and portability fix; it likely prevents subtle consensus bugs where different nodes might interpret a hash's numeric value differently, but the commit itself does not describe any active security incident or exploit.
Treat as a hardening/correctness patch. Include in releases because it touches consensus code, but no emergency response is indicated unless independent analysis shows the prior aliasing violation was exploitable to split consensus.
Security signals we found
Strict-aliasing/undefined-behavior fix in consensus-critical difficulty validation
Memory-safe load pattern introduced (memcpy + endianness swap)
Test code updated to use same safe pattern
No vendor statement of security relevance in commit message or diff
Evidence from the diff
The patch replaces reinterpret-cast-style reads ((const uint64_t *)&hash)[i] with a helper load_u64_from_hash that copies 8 bytes via memcpy into a uint64_t and then applies SWAP64LE. The same pattern is applied to test helpers. This avoids undefined behavior from strict-aliasing violations and potential unaligned access. In a consensus-critical function (check_hash_64 / check_hash_128), inconsistent hash interpretation across compilers/architectures could theoretically lead to consensus splits, but the diff only shows the defensive refactor and added tests, not a demonstrated vulnerability or exploit.
Changed components
src/cryptonote_basic/difficulty.cpptests/unit_tests/difficulty.cppInspect captured patch +28 / −10
diff --git a/src/cryptonote_basic/difficulty.cpp b/src/cryptonote_basic/difficulty.cpp
index ef94666..cf5a643 100644
--- a/src/cryptonote_basic/difficulty.cpp
+++ b/src/cryptonote_basic/difficulty.cpp
@@ -32,6 +32,7 @@
#include <cassert>
#include <cstddef>
#include <cstdint>
+#include <cstring>
#include <vector>
#include "int-util.h"
@@ -102,18 +103,25 @@ namespace cryptonote {
return a + b < a || (c && a + b == (uint64_t) -1);
}
+ static inline uint64_t load_u64_from_hash(const crypto::hash &hash, size_t index) {
+ assert(index < sizeof(hash.data) / sizeof(uint64_t));
+ uint64_t word;
+ memcpy(&word, hash.data + index * sizeof(word), sizeof(word));
+ return SWAP64LE(word);
+ }
+
bool check_hash_64(const crypto::hash &hash, uint64_t difficulty) {
uint64_t low, high, top, cur;
// First check the highest word, this will most likely fail for a random hash.
- mul(swap64le(((const uint64_t *) &hash)[3]), difficulty, top, high);
+ mul(load_u64_from_hash(hash, 3), difficulty, top, high);
if (high != 0) {
return false;
}
- mul(swap64le(((const uint64_t *) &hash)[0]), difficulty, low, cur);
- mul(swap64le(((const uint64_t *) &hash)[1]), difficulty, low, high);
+ mul(load_u64_from_hash(hash, 0), difficulty, low, cur);
+ mul(load_u64_from_hash(hash, 1), difficulty, low, high);
bool carry = cadd(cur, low);
cur = high;
- mul(swap64le(((const uint64_t *) &hash)[2]), difficulty, low, high);
+ mul(load_u64_from_hash(hash, 2), difficulty, low, high);
carry = cadc(cur, low, carry);
carry = cadc(high, top, carry);
return !carry;
@@ -171,7 +179,7 @@ namespace cryptonote {
bool check_hash_128(const crypto::hash &hash, difficulty_type difficulty) {
#ifndef FORCE_FULL_128_BITS
// fast check
- if (difficulty >= max64bit && ((const uint64_t *) &hash)[3] > 0)
+ if (difficulty >= max64bit && load_u64_from_hash(hash, 3) > 0)
return false;
#endif
// usual slow check
@@ -182,7 +190,7 @@ namespace cryptonote {
for(int i = 1; i < 4; i++) { // highest word is zero
#endif
hashVal <<= 64;
- hashVal |= swap64le(((const uint64_t *) &hash)[3 - i]);
+ hashVal |= load_u64_from_hash(hash, 3 - i);
}
return hashVal * difficulty <= max256bit;
}
diff --git a/tests/unit_tests/difficulty.cpp b/tests/unit_tests/difficulty.cpp
index c8e9cee..6a01eab 100644
--- a/tests/unit_tests/difficulty.cpp
+++ b/tests/unit_tests/difficulty.cpp
@@ -26,6 +26,9 @@
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#include <cstddef>
+#include <cstring>
+
#include "gtest/gtest.h"
#include "int-util.h"
#include "cryptonote_basic/difficulty.h"
@@ -37,6 +40,13 @@ static cryptonote::difficulty_type MKDIFF(uint64_t high, uint64_t low)
return d;
}
+static void set_hash_word(crypto::hash &hash, std::size_t index, uint64_t value)
+{
+ ASSERT_LT(index, sizeof(hash.data) / sizeof(value));
+ value = SWAP64LE(value);
+ memcpy(hash.data + index * sizeof(value), &value, sizeof(value));
+}
+
static crypto::hash MKHASH(uint64_t high, uint64_t low)
{
cryptonote::difficulty_type hash_target = high;
@@ -45,16 +55,16 @@ static crypto::hash MKHASH(uint64_t high, uint64_t low)
crypto::hash h;
uint64_t val;
val = (hash_value & 0xffffffffffffffff).convert_to<uint64_t>();
- ((uint64_t*)&h)[0] = SWAP64LE(val);
+ set_hash_word(h, 0, val);
hash_value >>= 64;
val = (hash_value & 0xffffffffffffffff).convert_to<uint64_t>();
- ((uint64_t*)&h)[1] = SWAP64LE(val);
+ set_hash_word(h, 1, val);
hash_value >>= 64;
val = (hash_value & 0xffffffffffffffff).convert_to<uint64_t>();
- ((uint64_t*)&h)[2] = SWAP64LE(val);
+ set_hash_word(h, 2, val);
hash_value >>= 64;
val = (hash_value & 0xffffffffffffffff).convert_to<uint64_t>();
- ((uint64_t*)&h)[3] = SWAP64LE(val);
+ set_hash_word(h, 3, val);
return h;
}
Why this scored 46/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.