Commit 3e8e5cb67f for openssl.org
commit 3e8e5cb67f563ecb292df35e16e90af418753d49
Author: Lukas Gerlach <s8lugerl@stud.uni-saarland.de>
Date: Wed Apr 15 15:52:46 2026 +0200
Fix constant-time violation in ossl_curve448_scalar_halve
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)
diff --git a/crypto/ec/curve448/scalar.c b/crypto/ec/curve448/scalar.c
index 191b0b4fd2..f70b24843f 100644
--- a/crypto/ec/curve448/scalar.c
+++ b/crypto/ec/curve448/scalar.c
@@ -210,6 +210,7 @@ void ossl_curve448_scalar_encode(unsigned char ser[C448_SCALAR_BYTES],
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;
diff --git a/crypto/ec/curve448/word.h b/crypto/ec/curve448/word.h
index 8137be6abb..a697879eeb 100644
--- a/crypto/ec/curve448/word.h
+++ b/crypto/ec/curve448/word.h
@@ -18,6 +18,7 @@
#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"
@@ -53,6 +54,12 @@ typedef int64_t dsword_t;
#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