Crypto: fix portable C code on big-endian CPUs (#1899)

* Crypto: fix portable C code on big-endian CPUs

On big-endian CPUs (e.g. MIPS and PowerPC) the portable C code of
several algorithms assumes a little-endian host, so ciphertext, hashes
and derived keys are wrong and "veracrypt --text --test" fails:

- Twofish: the key-dependent S-box bytes are read back from 32-bit
  words through a union, and blocks are loaded/stored as native words.
- Camellia: blocks and keys are always byte-swapped to big-endian.
- Kuznyechik: keys and blocks are loaded/stored as native 64-bit words
  while the tables expect the little-endian interpretation.
- SHA-256/SHA-512: message words, length and digest are always
  byte-swapped.
- BLAKE2s: the parameter block and message words are read as native
  little-endian data. The parameter block is now built from its fields,
  so it is right for any parameters, not only the ones VeraCrypt uses.
- Streebog: the existing big-endian code path is never enabled, and
  BSWAP64 used by it is not defined (its buffer512 initializer also has
  one pair of braces too many). Its add512() also differs from the
  little-endian one, which drops a carry on purpose (existing volumes
  depend on it); do the same limb arithmetic on big-endian.
- misc.h: the rotate-based bswap_32 (non-Linux, non-Apple, non-MSVC
  builds) lacks outer parentheses, so "bswap_32(a) ^ b" is parsed
  wrongly, and the fallback bswap_64 does not parenthesise its argument.
- PBKDF2: the block number is always byte-swapped.

Swap only where the host byte order differs from the data, and enable
the Streebog big-endian path. On big-endian hosts the message words,
keys and blocks of SHA-2, BLAKE2s, Twofish and Kuznyechik are read and
written byte-wise (new VcLoad*/VcStore* inline helpers in misc.h), so
these paths also work on misaligned buffers on strict-alignment CPUs.
On little-endian CPUs the code is unchanged: the object files of all
touched sources are byte-identical before and after this change
(checked on x86_64, aarch64 and mipsel). With it, all self-tests pass on
mips (big-endian) under qemu.

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

* Volume: avoid unaligned 64-bit header and test vector accesses

The volume header (de)serialisation and TestXtsAES read and write 64-bit
fields through casted pointers into byte buffers at offsets that are
only 4-byte aligned (e.g. the header field at offset 12). CPUs that trap
on misaligned access without kernel fix-up (SPARC, MIPS64 on some
systems, qemu-user) then fail with SIGBUS in "veracrypt --test" and when
creating or opening any volume. Use memcpy instead, and have
DeserializeEntryAt check that the whole entry, not only its start, lies
inside the header.

The XTS code also accesses its local byte arrays (whitening values,
data unit number) through uint64 pointers; declare them 8-byte aligned
instead of relying on the compiler's stack layout.

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

* Volume: test multi-block PBKDF2 output and the Streebog carry case

The PBKDF2 self-tests only check the first 4 bytes of each derivation,
so the block counter of the second and later output blocks is never
checked. Also compare the complete output of a 192-byte derivation for
every PRF, which covers the later blocks and how they are joined, and
hash a message whose block sum hits the carry case of Streebog's
add512(), which has to stay bit-compatible across CPUs. The message
buffer is 8-byte aligned, as the portable Streebog code reads it as
64-bit words. The expected values are those of the existing
little-endian code, and match independent implementations for all five
PRFs (Python hashlib/hmac, OpenSSL for Whirlpool, gostcrypto for
Streebog).

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 authored and GitHub committed 2026-10-04 09:51:59 +09:00
1 parent ce72be65bb
commit 425cea6c9c
11 files changed
+353 -72

No files matched your search

+13 -5
View File
@@ -22,6 +22,14 @@
#include "Whirlpool.h"
#include "cpu.h"
#include "misc.h"
#include "Endian.h"
/* PBKDF2 block numbers are big-endian: swap only on little-endian hosts. */
#if BYTE_ORDER == BIG_ENDIAN
#define PKCS5_BE32(x) (x)
#else
#define PKCS5_BE32(x) (bswap_32(x))
#endif
#else
#pragma optimize ("t", on)
#include <string.h>
@@ -180,7 +188,7 @@ static void derive_u_sha256 (const unsigned char *salt, int salt_len, uint32 ite
memset (&k[salt_len], 0, 3);
k[salt_len + 3] = (unsigned char) b;
#else
b = bswap_32 (b);
b = PKCS5_BE32 (b);
memcpy (&k[salt_len], &b, 4);
#endif
@@ -433,7 +441,7 @@ static void derive_u_sha512 (const unsigned char *salt, int salt_len, uint32 ite
/* iteration 1 */
memcpy (k, salt, salt_len); /* salt */
/* big-endian block number */
b = bswap_32 (b);
b = PKCS5_BE32 (b);
memcpy (&k[salt_len], &b, 4);
hmac_sha512_internal (k, salt_len + 4, hmac);
@@ -681,7 +689,7 @@ static void derive_u_blake2s (const unsigned char *salt, int salt_len, uint32 it
memset (&k[salt_len], 0, 3);
k[salt_len + 3] = (unsigned char) b;
#else
b = bswap_32 (b);
b = PKCS5_BE32 (b);
memcpy (&k[salt_len], &b, 4);
#endif
@@ -911,7 +919,7 @@ static void derive_u_whirlpool (const unsigned char *salt, int salt_len, uint32
/* iteration 1 */
memcpy (k, salt, salt_len); /* salt */
/* big-endian block number */
b = bswap_32 (b);
b = PKCS5_BE32 (b);
memcpy (&k[salt_len], &b, 4);
hmac_whirlpool_internal (k, salt_len + 4, hmac);
@@ -1114,7 +1122,7 @@ static void derive_u_streebog (const unsigned char *salt, int salt_len, uint32 i
/* iteration 1 */
memcpy (k, salt, salt_len); /* salt */
/* big-endian block number */
b = bswap_32 (b);
b = PKCS5_BE32 (b);
memcpy (&k[salt_len], &b, 4);
hmac_streebog_internal (k, salt_len + 4, hmac);