Add a value barrier to the mask variable in ossl_curve448_scalar_halve()
to prevent LLVM SimpleLoopUnswitchPass from introducing a
secret-dependent branch.
When compiled with Clang >= 17 at -O3, the mask which is static during
the loop (derived from the secret scalar LSB) is used by SimpleLoopUnswitchPass
to clone the loop body into two versions guarded by a branch on the secret bit.
This produces a side-channel that leaks nonce parity.
The value barrier forces the compiler to treat the mask as opaque,
preventing loop unswitching while maintaining identical performance.
A portable value_barrier_c448 macro is added to word.h to select the
appropriate barrier width (32 or 64 bit) based on C448_WORD_BITS.
Reviewed-by: Kurt Roeckx <kurt@roeckx.be>
Reviewed-by: Paul Dale <paul.dale@oracle.com>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Thu Apr 16 16:43:22 2026
(Merged from https://github.com/openssl/openssl/pull/30845)
void ossl_curve448_scalar_halve(curve448_scalar_t out, const curve448_scalar_t a)
{
c448_word_t mask = 0 - (a->limb[0] & 1);
+ mask = value_barrier_c448(mask);
c448_dword_t chain = 0;
unsigned int i;
#include <stdlib.h>
#include <openssl/e_os2.h>
#include "curve448utils.h"
+#include "internal/constant_time.h"
#ifdef INT128_MAX
#include "arch_64/arch_intrinsics.h"
#error "For now we only support 32- and 64-bit architectures."
#endif
+#if C448_WORD_BITS == 64
+#define value_barrier_c448(x) value_barrier_64(x)
+#elif C448_WORD_BITS == 32
+#define value_barrier_c448(x) value_barrier_32(x)
+#endif
+
/*
* The plan on booleans: The external interface uses c448_bool_t, but this
* might be a different size than our particular arch's word_t (and thus