Crypto: use alignment-safe loads and stores where unaligned access faults (#1909)

* Crypto: use alignment-safe loads and stores on all hosts

SHA-2, BLAKE2s, Twofish and Kuznyechik read and write words in caller
buffers directly on little-endian hosts, Streebog's stage2() reads 64-bit
words from the input, and the Unix XTS code XORs whitening values through
uint64 pointers. On strict-alignment CPUs (ARMv5-v7 for LDRD/LDM, MIPS,
mips64) this raises SIGBUS when a caller passes an unaligned buffer; real
volume operations use aligned buffers, but test vectors and future
callers need not.

- SHA-2, BLAKE2s, Twofish and Kuznyechik use the VcLoad*/VcStore* helpers
  on all hosts, not only on big-endian ones.
- On GCC/Clang little-endian targets the helpers copy with memcpy, which
  compiles to single loads and stores where unaligned access is allowed,
  so the C paths keep their speed. MSVC targets keep native accesses, as
  before.
- Streebog takes the direct path only for 8-byte-aligned input; other
  input goes through the existing copy into CTX->buffer.
- XTS (Unix, without the x64 SSE path) XORs the whitening values through
  memcpy.

Output is unchanged: known-answer comparisons for every offset from 0 to
15 match on x86_64, armv7, mips, mipsel and mips64, and an
alignment-checking (UBSan) build is clean.

Signed-off-by: Ville Takio <ville+git@takio.fi>

* Crypto: keep direct word accesses where unaligned access is safe

Address review: limit the alignment fix to the targets that need it.

VC_UNALIGNED_ACCESS_OK (config.h) is 1 on x86, x64, ARMv8 (including
MSVC ARM64) and PowerPC, where unaligned 32- and 64-bit accesses are
safe; there SHA-2, BLAKE2s, Twofish, Kuznyechik, Streebog and XTS
compile exactly as before. On other targets (32-bit ARM, whose
unaligned support does not cover LDRD/LDM, MIPS, ...), they load and
store through the VcLoad*/VcStore* helpers; SHA-2 keeps its byte swaps
and only the input load changes.

The object code of the changed files is identical to master with GCC
and Clang on x86_64 and with GCC on AArch64. On armv7, mips, mipsel and
mips64 (qemu-user), the known-answer outputs at buffer offsets 0..15
match x86_64.

Signed-off-by: Ville Takio <ville+git@takio.fi>

---------

Signed-off-by: Ville Takio <ville+git@takio.fi>
This commit is contained in:
Ville Takio
2026-10-09 20:39:21 +09:00
committed by GitHub
parent cb1d9a4c3b
commit 7c06e03ec3
8 changed files with 90 additions and 8 deletions
+5 -1
View File
@@ -23,9 +23,13 @@ and released into public domain.
#if BYTE_ORDER == BIG_ENDIAN
#define SHA2_LOAD_BE32(p, i) VcLoadBE32((const uint8 *) (p) + 4 * (i))
#define SHA2_LOAD_BE64(p, i) VcLoadBE64((const uint8 *) (p) + 8 * (i))
#else
#elif VC_UNALIGNED_ACCESS_OK
#define SHA2_LOAD_BE32(p, i) bswap_32(((const uint_32t *) (p))[i])
#define SHA2_LOAD_BE64(p, i) bswap_64(((const uint_64t *) (p))[i])
#else
/* The caller's buffer may be unaligned: load it alignment-safe, then swap. */
#define SHA2_LOAD_BE32(p, i) bswap_32(VcLoadLE32((const uint8 *) (p) + 4 * (i)))
#define SHA2_LOAD_BE64(p, i) bswap_64(VcLoadLE64((const uint8 *) (p) + 8 * (i)))
#endif
#if defined(_UEFI) || defined(CRYPTOPP_DISABLE_ASM)
+4 -1
View File
@@ -2266,7 +2266,10 @@ void STREEBOG_add(STREEBOG_CTX *CTX, const uint8 *data, size_t len)
{
size_t chunksize;
while (len > 63 && CTX->bufsize == 0)
/* stage2() reads the block as 64-bit words: where unaligned access is not
* safe, unaligned input is copied to the buffer first */
while (len > 63 && CTX->bufsize == 0
&& (VC_UNALIGNED_ACCESS_OK || ((size_t) data % sizeof (unsigned long long)) == 0))
{
stage2(CTX, data);
+6
View File
@@ -51,8 +51,14 @@
#define TWOFISH_STORE(p, i, v) VcStoreLE32((p) + (i), (v))
#else
#define TWOFISH_LE32(x) (x)
#if VC_UNALIGNED_ACCESS_OK
#define TWOFISH_LOAD(p, i) ((p)[i])
#define TWOFISH_STORE(p, i, v) ((p)[i] = (v))
#else
/* Blocks may be unaligned in the caller's buffer. */
#define TWOFISH_LOAD(p, i) VcLoadLE32((p) + (i))
#define TWOFISH_STORE(p, i, v) VcStoreLE32((p) + (i), (v))
#endif
#endif
/* C implementation based on code written by kerukuro for cppcrypto library
+4 -3
View File
@@ -28,8 +28,9 @@
// load32 is always called in SSE case which implies little endian
#define load32(x) *((uint32*) (x))
/* Message words are little-endian: read them byte-wise on big-endian hosts. */
#if BYTE_ORDER == BIG_ENDIAN
/* Message words are little-endian: read them byte-wise on big-endian hosts and
* alignment-safe where unaligned access is not safe. */
#if BYTE_ORDER == BIG_ENDIAN || !VC_UNALIGNED_ACCESS_OK
#define BLAKE2S_LOAD32(p) VcLoadLE32(p)
#else
#define BLAKE2S_LOAD32(p) (*((uint32*) (p)))
@@ -273,7 +274,7 @@ int blake2s_final( blake2s_state *S, unsigned char *out )
for( i = 0; i < 8; ++i ) /* Output full hash to temp buffer */
{
#if BYTE_ORDER == LITTLE_ENDIAN
#if BYTE_ORDER == LITTLE_ENDIAN && VC_UNALIGNED_ACCESS_OK
*((uint32*) out) = S->h[i];
#else
uint32 w = S->h[i] ;
+11
View File
@@ -216,6 +216,17 @@
#define CRYPTOPP_BOOL_ARM64 0
#endif
// VC_UNALIGNED_ACCESS_OK is 1 on targets where unaligned 32- and 64-bit loads
// and stores are safe (x86, x64, ARMv8, PowerPC), which keep their direct word
// accesses. Elsewhere (e.g. 32-bit ARM, whose unaligned support does not cover
// the 64-bit LDRD/LDM, and MIPS), code that may get unaligned buffers uses the
// alignment-safe VcLoad*/VcStore* helpers from misc.h.
#if CRYPTOPP_BOOL_X64 || CRYPTOPP_BOOL_X86 || CRYPTOPP_BOOL_X32 || CRYPTOPP_BOOL_ARMV8 || defined(__powerpc__)
#define VC_UNALIGNED_ACCESS_OK 1
#else
#define VC_UNALIGNED_ACCESS_OK 0
#endif
// ARMv8 and ASIMD. -march=armv8-a or above must be present
// Requires GCC 4.8, Clang 3.3 or Visual Studio 2017
// Do not use APPLE_CLANG_VERSION; use __ARM_FEATURE_XXX instead.
+3 -2
View File
@@ -8,8 +8,9 @@ and released into public domain.
#include "misc.h"
#include "Common/Endian.h"
/* Keys and blocks are used as little-endian words: swap only on big-endian hosts. */
#if BYTE_ORDER == BIG_ENDIAN
/* Keys and blocks are used as little-endian words: swap on big-endian hosts,
* and access them alignment-safe where unaligned access is not safe. */
#if BYTE_ORDER == BIG_ENDIAN || !VC_UNALIGNED_ACCESS_OK
#define KUZNYECHIK_LOAD64(p) VcLoadLE64(p)
#define KUZNYECHIK_STORE64(p, v) VcStoreLE64((p), (v))
#else
+27 -1
View File
@@ -113,11 +113,23 @@ extern "C" {
#endif
/* Alignment-safe loads and stores of little- and big-endian words in byte
* buffers (used by the big-endian code paths). */
* buffers (used by the big-endian code paths and where unaligned access is
* not safe). On little-endian GCC/Clang targets, memcpy compiles to the
* fastest access the target allows. */
#if defined(__GNUC__) && defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
# define VC_LE_ACCESS_MEMCPY
#endif
VC_INLINE uint32 VcLoadLE32 (const void *ptr)
{
#ifdef VC_LE_ACCESS_MEMCPY
uint32 value;
__builtin_memcpy (&value, ptr, sizeof (value));
return value;
#else
const uint8 *p = (const uint8 *) ptr;
return (uint32) p[0] | ((uint32) p[1] << 8) | ((uint32) p[2] << 16) | ((uint32) p[3] << 24);
#endif
}
VC_INLINE uint32 VcLoadBE32 (const void *ptr)
@@ -128,18 +140,28 @@ VC_INLINE uint32 VcLoadBE32 (const void *ptr)
VC_INLINE void VcStoreLE32 (void *ptr, uint32 value)
{
#ifdef VC_LE_ACCESS_MEMCPY
__builtin_memcpy (ptr, &value, sizeof (value));
#else
uint8 *p = (uint8 *) ptr;
p[0] = (uint8) value;
p[1] = (uint8) (value >> 8);
p[2] = (uint8) (value >> 16);
p[3] = (uint8) (value >> 24);
#endif
}
#ifndef TC_NO_COMPILER_INT64
VC_INLINE uint64 VcLoadLE64 (const void *ptr)
{
#ifdef VC_LE_ACCESS_MEMCPY
uint64 value;
__builtin_memcpy (&value, ptr, sizeof (value));
return value;
#else
const uint8 *p = (const uint8 *) ptr;
return (uint64) VcLoadLE32 (p) | ((uint64) VcLoadLE32 (p + 4) << 32);
#endif
}
VC_INLINE uint64 VcLoadBE64 (const void *ptr)
@@ -150,9 +172,13 @@ VC_INLINE uint64 VcLoadBE64 (const void *ptr)
VC_INLINE void VcStoreLE64 (void *ptr, uint64 value)
{
#ifdef VC_LE_ACCESS_MEMCPY
__builtin_memcpy (ptr, &value, sizeof (value));
#else
uint8 *p = (uint8 *) ptr;
VcStoreLE32 (p, (uint32) value);
VcStoreLE32 (p + 4, (uint32) (value >> 32));
#endif
}
#endif
+30
View File
@@ -47,6 +47,20 @@
namespace VeraCrypt
{
#if !VC_UNALIGNED_ACCESS_OK
// The data buffer is supplied by the caller and need not be 64-bit aligned.
static inline void XorWhiteningValue (uint64 *&data, uint64 *&whiteningValue)
{
uint64 block[2];
memcpy (block, data, sizeof (block));
block[0] ^= whiteningValue[0];
block[1] ^= whiteningValue[1];
memcpy (data, block, sizeof (block));
data += 2;
whiteningValue += 2;
}
#endif
void EncryptionModeXTS::Encrypt (uint8 *data, uint64 length) const
{
EncryptBuffer (data, length, 0);
@@ -177,8 +191,12 @@ namespace VeraCrypt
for (block = 0; block < countBlock; block++)
{
// Pre-whitening
#if VC_UNALIGNED_ACCESS_OK
*bufPtr++ ^= *whiteningValuesPtr64++;
*bufPtr++ ^= *whiteningValuesPtr64++;
#else
XorWhiteningValue (bufPtr, whiteningValuesPtr64);
#endif
}
#endif
// Actual encryption
@@ -193,8 +211,12 @@ namespace VeraCrypt
for (block = 0; block < countBlock; block++)
{
// Post-whitening
#if VC_UNALIGNED_ACCESS_OK
*bufPtr++ ^= *whiteningValuesPtr64++;
*bufPtr++ ^= *whiteningValuesPtr64++;
#else
XorWhiteningValue (bufPtr, whiteningValuesPtr64);
#endif
}
#endif
remainingBlocks -= countBlock;
@@ -348,8 +370,12 @@ namespace VeraCrypt
#else
for (block = 0; block < countBlock; block++)
{
#if VC_UNALIGNED_ACCESS_OK
*bufPtr++ ^= *whiteningValuesPtr64++;
*bufPtr++ ^= *whiteningValuesPtr64++;
#else
XorWhiteningValue (bufPtr, whiteningValuesPtr64);
#endif
}
#endif
cipher.DecryptBlocks ((uint8 *) dataUnitBufPtr, countBlock);
@@ -361,8 +387,12 @@ namespace VeraCrypt
#else
for (block = 0; block < countBlock; block++)
{
#if VC_UNALIGNED_ACCESS_OK
*bufPtr++ ^= *whiteningValuesPtr64++;
*bufPtr++ ^= *whiteningValuesPtr64++;
#else
XorWhiteningValue (bufPtr, whiteningValuesPtr64);
#endif
}
#endif
remainingBlocks -= countBlock;