Skip to content

Argon2: opt_sse2.c fails to compile when the global CFLAGS enable AVX2 (-march=native / -march=x86-64-v3) #1867

Description

@Esdex

Expected behavior

make succeeds when the distribution's global CFLAGS target an x86-64 level
that includes AVX2 (-march=x86-64-v3, or -march=native on any AVX2 CPU).
These are the stock compiler flags on CachyOS, Clear Linux, ALHP and any
source-based or -march-tuned distribution, and they are passed to the build
through TC_EXTRA_CFLAGS.

Observed behavior

The build aborts while compiling the Argon2 SSE2 translation unit:

Compiling opt_sse2.c
../Crypto/Argon2/src/opt_sse2.c: In function 'fill_block':
../Crypto/Argon2/src/opt_sse2.c:61:9: error: implicit declaration of function 'BLAKE2_ROUND'; did you mean 'BLAKE2_ROUND_1'? [-Wimplicit-function-declaration]
   61 |         BLAKE2_ROUND(state[8 * i + 0], state[8 * i + 1], state[8 * i + 2],
      |         ^~~~~~~~~~~~
      |         BLAKE2_ROUND_1
make[1]: *** [.../Build/Include/Makefile.inc:20: ../Crypto/Argon2/src/opt_sse2.o] Error 1

This has been an error rather than a warning since GCC 14, which promoted
-Wimplicit-function-declaration to an error by default. On older compilers the
same code would have produced an unresolved symbol at link time, so the SSE2
path was never actually usable under these flags.

Root cause

Crypto/Argon2/src/blake2/blamka-round-opt.h selects its implementation from
the predefined ISA macros:

condition what the header defines
!__AVX512F__ && !__AVX2__ BLAKE2_ROUND on __m128i (line 170)
__AVX2__ only BLAKE2_ROUND_1 / BLAKE2_ROUND_2 on __m256i (lines 303, 316)
__AVX512F__ BLAKE2_ROUND, BLAKE2_ROUND_1, BLAKE2_ROUND_2 on __m512i

opt_sse2.c is the 128-bit implementation and its fill_block() needs the
__m128i BLAKE2_ROUND. It is compiled by the generic %.o: %.c rule with the
unmodified global $(CFLAGS), so when those carry AVX2 the header hands it the
__m256i branch instead and the macro it needs is never defined.

opt_avx2.c does not have this problem: Volume/Volume.make:111 routes it to
OBJSAVX2 with the .oavx2 suffix, and the %.oavx2 rule at
Build/Include/Makefile.inc:43-45 appends -mavx2 explicitly, so that TU always
gets the ISA level it was written for regardless of the global flags. opt_sse2.c has no corresponding rule pinning
it to the SSE2 baseline; it is simply assumed that the global flags never
exceed SSE2.

Worth noting that this is specific to the VeraCrypt integration rather than a
defect in Argon2 itself. Upstream P-H-C/phc-winner-argon2 ships a single
opt.c and has no runtime dispatch, so there the header's macro-driven
selection is unambiguous: the one optimized TU is compiled at whatever ISA level
the build targets. Splitting it into opt_sse2.c + opt_avx2.c with the
dispatcher in ref.c created the requirement that each TU be compiled at a
fixed, different ISA level, and only one of the two got an explicit rule.

Steps to reproduce

A single compiler invocation is enough; no packaging or distro tooling involved.
From src/ of a clean 1.26.29 checkout:

# succeeds
gcc -march=x86-64    -DARGON2_NO_THREADS -I. -I./Crypto -I./Crypto/Argon2/include \
    -c Crypto/Argon2/src/opt_sse2.c -o /tmp/ok.o

# fails with the error above
gcc -march=x86-64-v3 -DARGON2_NO_THREADS -I. -I./Crypto -I./Crypto/Argon2/include \
    -c Crypto/Argon2/src/opt_sse2.c -o /tmp/fail.o

Equivalently, a full build: make TC_EXTRA_CFLAGS="-march=x86-64-v3".

Suggested fix

Give opt_sse2.c an explicit baseline the same way opt_avx2.c gets an
explicit -mavx2. The smallest form is a target-specific variable in
Volume/Volume.make, next to the existing entry on line 137:

 OBJS += ../Crypto/Argon2/src/opt_sse2.o
+
+# Select the __m128i branch of blamka-round-opt.h regardless of the -march level
+# in the global CFLAGS. Appended after $(TC_EXTRA_CFLAGS), so it wins.
+../Crypto/Argon2/src/opt_sse2.o: CFLAGS += -mno-avx2 -mno-avx512f

This works because Makefile:588 places $(TC_EXTRA_CFLAGS) at the end of
CFLAGS, and the target-specific += appends after that, so the later flag
wins. A .osse2 suffix rule alongside the existing .oavx2 / .ossse3 /
.oaesni rules in Build/Include/Makefile.inc would be more consistent with
the surrounding style, at the cost of also threading a new OBJSSSE2 variable
through the clean target, the archive rule and the dependency include.

I have been running 1.26.29 built with the patch above on -march=native
(AVX2, no AVX512). veracrypt --text --test passes, and an Argon2 volume
created, dismounted, remounted and byte-compared round-trips correctly.

Your Environment

VeraCrypt version: 1.26.29 (also reproduces on current master; both files
are byte-identical to the tag)
Operating system and version: CachyOS (Arch-based), Linux 7.2.2
System type: 64-bit
Compiler: GCC 16.2.1
Relevant flags: CFLAGS="-march=native -O3 -flto=auto" from
/etc/makepkg.conf; the CPU has AVX2 but not AVX512.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions