new file mode 100644
@@ -0,0 +1,182 @@
+From 8c3f7b9c7f7efb9e8539c2d98fe52e116f3402eb Mon Sep 17 00:00:00 2001
+From: Sean Parkinson <sean@wolfssl.com>
+Date: Wed, 27 May 2026 12:16:06 +1000
+Subject: [PATCH] X25519 x64 ASM: fix full reduction
+
+The last add was overflowing into the top bit.
+Must mask the last word to clear top bit.
+
+Add test vectors from Wycheproof.
+
+(cherry picked from commit 14b55a0bc42c4f2d2fa0a06af53a603ed619c9b0)
+
+CVE: CVE-2026-10512
+Upstream-Status: Backport [https://github.com/wolfSSL/wolfssl/commit/14b55a0bc42c4f2d2fa0a06af53a603ed619c9b0]
+
+Signed-off-by: Ankur Tyagi <ankur.tyagi85@gmail.com>
+---
+ tests/api/test_curve25519.c | 93 +++++++++++++++++++++++++++++++++++
+ tests/api/test_curve25519.h | 2 +
+ wolfcrypt/src/fe_x25519_asm.S | 4 ++
+ 3 files changed, 99 insertions(+)
+
+diff --git a/tests/api/test_curve25519.c b/tests/api/test_curve25519.c
+index 36cf643f2..36c8e58af 100644
+--- a/tests/api/test_curve25519.c
++++ b/tests/api/test_curve25519.c
+@@ -353,6 +353,99 @@ int test_wc_curve25519_shared_secret_ex(void)
+ return EXPECT_RESULT();
+ } /* END test_wc_curve25519_shared_secret_ex */
+
++/*
++ * Known-answer tests for wc_curve25519_shared_secret_ex.
++ *
++ * Both vectors share one private scalar and produce a shared secret that is a
++ * small canonical value (9 and 16, little-endian). Because the result is close
++ * to a multiple of the field prime, these exercise the final modular reduction
++ * of the X25519 computation: a result that was only reduced mod 2^256 (or left
++ * in [p, 2^255)) instead of fully reduced mod 2^255-19 would not match.
++ * All values are 32-byte little-endian encodings per RFC 7748.
++ */
++int test_wc_curve25519_shared_secret_ex_kat(void)
++{
++ EXPECT_DECLS;
++#if defined(HAVE_CURVE25519) && defined(HAVE_CURVE25519_KEY_IMPORT)
++ /* Private scalar shared by both vectors. */
++ static const byte kPriv[CURVE25519_KEYSIZE] = {
++ 0x60, 0xa3, 0xa4, 0xf1, 0x30, 0xb9, 0x8a, 0x5b,
++ 0xe4, 0xb1, 0xce, 0xdb, 0x7c, 0xb8, 0x55, 0x84,
++ 0xa3, 0x52, 0x0e, 0x14, 0x2d, 0x47, 0x4d, 0xc9,
++ 0xcc, 0xb9, 0x09, 0xa0, 0x73, 0xa9, 0x76, 0x7f
++ };
++ /* Vector 1 public value, expected shared secret == 9. */
++ static const byte kPub1[CURVE25519_KEYSIZE] = {
++ 0x3b, 0x18, 0xdf, 0x1e, 0x50, 0xb8, 0x99, 0xeb,
++ 0xd5, 0x88, 0xc3, 0x16, 0x1c, 0xbd, 0x3b, 0xf9,
++ 0x8e, 0xbc, 0xc2, 0xc1, 0xf7, 0xdf, 0x53, 0xb8,
++ 0x11, 0xbd, 0x0e, 0x91, 0xb4, 0xd5, 0x15, 0x3d
++ };
++ static const byte kExpected1[CURVE25519_KEYSIZE] = {
++ 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
++ };
++ /* Vector 2 public value, expected shared secret == 16. */
++ static const byte kPub2[CURVE25519_KEYSIZE] = {
++ 0xca, 0xb6, 0xf9, 0xe7, 0xd8, 0xce, 0x00, 0xdf,
++ 0xce, 0xa9, 0xbb, 0xd8, 0xf0, 0x69, 0xef, 0x7f,
++ 0xb2, 0xac, 0x50, 0x4a, 0xbf, 0x83, 0xb8, 0x7d,
++ 0xb6, 0x01, 0xb5, 0xae, 0x0a, 0x7f, 0x76, 0x15
++ };
++ static const byte kExpected2[CURVE25519_KEYSIZE] = {
++ 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
++ };
++ /* Table-driven so both vectors run through the identical code path. */
++ struct {
++ const byte* pub;
++ const byte* expected;
++ } vec[2];
++ curve25519_key private_key;
++ curve25519_key public_key;
++ WC_RNG rng;
++ byte out[CURVE25519_KEYSIZE];
++ word32 outLen;
++ int i;
++
++ vec[0].pub = kPub1; vec[0].expected = kExpected1;
++ vec[1].pub = kPub2; vec[1].expected = kExpected2;
++
++ XMEMSET(&rng, 0, sizeof(WC_RNG));
++ ExpectIntEQ(wc_InitRng(&rng), 0);
++
++ for (i = 0; i < 2; i++) {
++ XMEMSET(&private_key, 0, sizeof(private_key));
++ XMEMSET(&public_key, 0, sizeof(public_key));
++ ExpectIntEQ(wc_curve25519_init(&private_key), 0);
++ ExpectIntEQ(wc_curve25519_init(&public_key), 0);
++ #ifdef WOLFSSL_CURVE25519_BLINDING
++ ExpectIntEQ(wc_curve25519_set_rng(&private_key, &rng), 0);
++ #endif
++ ExpectIntEQ(wc_curve25519_import_private_ex(kPriv, sizeof(kPriv),
++ &private_key, EC25519_LITTLE_ENDIAN), 0);
++ ExpectIntEQ(wc_curve25519_import_public_ex(vec[i].pub,
++ CURVE25519_KEYSIZE, &public_key, EC25519_LITTLE_ENDIAN), 0);
++
++ outLen = sizeof(out);
++ ExpectIntEQ(wc_curve25519_shared_secret_ex(&private_key, &public_key,
++ out, &outLen, EC25519_LITTLE_ENDIAN), 0);
++ ExpectIntEQ(outLen, CURVE25519_KEYSIZE);
++ ExpectIntEQ(XMEMCMP(out, vec[i].expected, CURVE25519_KEYSIZE), 0);
++
++ wc_curve25519_free(&private_key);
++ wc_curve25519_free(&public_key);
++ }
++
++ DoExpectIntEQ(wc_FreeRng(&rng), 0);
++#endif
++ return EXPECT_RESULT();
++} /* END test_wc_curve25519_shared_secret_ex_kat */
++
+ /*
+ * Testing wc_curve25519_make_pub
+ */
+diff --git a/tests/api/test_curve25519.h b/tests/api/test_curve25519.h
+index 0e0e65287..770b509af 100644
+--- a/tests/api/test_curve25519.h
++++ b/tests/api/test_curve25519.h
+@@ -30,6 +30,7 @@ int test_wc_curve25519_export_key_raw(void);
+ int test_wc_curve25519_export_key_raw_ex(void);
+ int test_wc_curve25519_make_key(void);
+ int test_wc_curve25519_shared_secret_ex(void);
++int test_wc_curve25519_shared_secret_ex_kat(void);
+ int test_wc_curve25519_make_pub(void);
+ int test_wc_curve25519_export_public_ex(void);
+ int test_wc_curve25519_export_private_raw_ex(void);
+@@ -43,6 +44,7 @@ int test_wc_curve25519_import_private(void);
+ TEST_DECL_GROUP("curve25519", test_wc_curve25519_export_key_raw_ex), \
+ TEST_DECL_GROUP("curve25519", test_wc_curve25519_make_key), \
+ TEST_DECL_GROUP("curve25519", test_wc_curve25519_shared_secret_ex), \
++ TEST_DECL_GROUP("curve25519", test_wc_curve25519_shared_secret_ex_kat), \
+ TEST_DECL_GROUP("curve25519", test_wc_curve25519_make_pub), \
+ TEST_DECL_GROUP("curve25519", test_wc_curve25519_export_public_ex), \
+ TEST_DECL_GROUP("curve25519", test_wc_curve25519_export_private_raw_ex), \
+diff --git a/wolfcrypt/src/fe_x25519_asm.S b/wolfcrypt/src/fe_x25519_asm.S
+index f4cdf343c..5abe4cd5e 100644
+--- a/wolfcrypt/src/fe_x25519_asm.S
++++ b/wolfcrypt/src/fe_x25519_asm.S
+@@ -4639,6 +4639,7 @@ L_curve25519_base_x64_3:
+ adcq $0x00, %r8
+ adcq $0x00, %r9
+ adcq $0x00, %r10
++ andq %rax, %r10
+ # Store
+ movq %rcx, (%rdi)
+ movq %r8, 8(%rdi)
+@@ -7054,6 +7055,7 @@ L_curve25519_x64_3:
+ adcq $0x00, %r9
+ adcq $0x00, %r10
+ adcq $0x00, %r11
++ andq %rax, %r11
+ # Store
+ movq %rcx, (%rdi)
+ movq %r9, 8(%rdi)
+@@ -15107,6 +15109,7 @@ L_curve25519_base_avx2_last_3:
+ adcq $0x00, %r9
+ adcq $0x00, %r10
+ adcq $0x00, %r11
++ andq %rcx, %r11
+ # Store
+ movq %r8, (%rdi)
+ movq %r9, 8(%rdi)
+@@ -17116,6 +17119,7 @@ L_curve25519_avx2_last_3:
+ adcq $0x00, %r10
+ adcq $0x00, %r11
+ adcq $0x00, %r12
++ andq %rcx, %r12
+ # Store
+ movq %r9, (%rdi)
+ movq %r10, 8(%rdi)
@@ -17,6 +17,7 @@ SRC_URI = " \
file://run-ptest \
file://CVE-2026-10098-1.patch \
file://CVE-2026-10098-2.patch \
+ file://CVE-2026-10512.patch \
"
SRCREV = "1d363f3adceba9d1478230ede476a37b0dcdef24"