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