From 61b6c838514b30fe80ff46b521e0fbe97f3f43e8 Mon Sep 17 00:00:00 2001 From: Yosuke Shimizu Date: Mon, 3 Aug 2026 15:51:27 +0900 Subject: [PATCH] Validate X25519 public key length before reading input buffer --- src/wp_ecx_kmgmt.c | 6 ++++ test/test_ecx.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++ test/unit.c | 1 + test/unit.h | 1 + 4 files changed, 79 insertions(+) diff --git a/src/wp_ecx_kmgmt.c b/src/wp_ecx_kmgmt.c index d7386a8c..8af60cc7 100644 --- a/src/wp_ecx_kmgmt.c +++ b/src/wp_ecx_kmgmt.c @@ -1433,6 +1433,7 @@ const OSSL_DISPATCH wp_##alg##_keymgmt_functions[] = { \ * @param [in, out] key wolfSSL X25519 key object. * @param [in] endian Which endian the bytes are in. Unused. * @return 0 on success. + * @return BAD_FUNC_ARG when in is NULL or inLen is not the key size. * @return -ve on failure. */ static int wp_x25519_import_public(const byte* in, word32 inLen, @@ -1440,6 +1441,11 @@ static int wp_x25519_import_public(const byte* in, word32 inLen, { unsigned char data[CURVE25519_KEYSIZE]; + /* Length must be checked before in is read below. */ + if ((in == NULL) || (inLen != CURVE25519_KEYSIZE)) { + return BAD_FUNC_ARG; + } + /* OpenSSL masks off top bit of public key. */ if ((in[CURVE25519_KEYSIZE - 1] & 0x80) != 0x00) { XMEMCPY(data, in, CURVE25519_KEYSIZE); diff --git a/test/test_ecx.c b/test/test_ecx.c index 8f4391e1..cd3bce54 100644 --- a/test/test_ecx.c +++ b/test/test_ecx.c @@ -933,6 +933,77 @@ int test_ecx_import_zero_priv(void *data) OPENSSL_free(buf); return err; } + +/* An undersized public key octet string must be rejected without an + * out-of-bounds read (in[31] and a 32-byte copy out of a shorter buffer). + * The buffer is heap allocated so ASan sees the overread. */ +int test_ecx_import_short_pub(void *data) +{ + int err = 0; + EVP_PKEY_CTX *ctx = NULL; + EVP_PKEY *pkey = NULL; + EVP_PKEY *setPkey = NULL; + unsigned char *buf = NULL; + /* RFC 7748 Section 6.1 Alice private key. */ + static const unsigned char privKey[] = { + 0x77, 0x07, 0x6d, 0x0a, 0x73, 0x18, 0xa5, 0x7d, + 0x3c, 0x16, 0xc1, 0x72, 0x51, 0xb2, 0x66, 0x45, + 0xdf, 0x4c, 0x2f, 0x87, 0xeb, 0xc0, 0x99, 0x2a, + 0xb1, 0x77, 0xfb, 0xa5, 0x1d, 0xb9, 0x2c, 0x2a + }; + /* Deliberately shorter than CURVE25519_KEYSIZE. */ + const size_t shortLen = 4; + OSSL_PARAM params[2]; + + (void)data; + + PRINT_MSG("X25519 import of undersized public key"); + + buf = OPENSSL_malloc(shortLen); + if (buf == NULL) { + err = 1; + } + if (err == 0) { + /* Top bit of the last byte set is what triggers the 32-byte copy. */ + memset(buf, 0xff, shortLen); + ctx = EVP_PKEY_CTX_new_from_name(wpLibCtx, "X25519", NULL); + err = ctx == NULL; + } + if (err == 0) { + err = EVP_PKEY_fromdata_init(ctx) != 1; + } + if (err == 0) { + params[0] = OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_PUB_KEY, + buf, shortLen); + params[1] = OSSL_PARAM_construct_end(); + if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) == 1) { + PRINT_ERR_MSG("X25519 fromdata accepted short public key"); + err = 1; + } + } + + /* Same undersized buffer through the encoded public key set_params path. */ + if (err == 0) { + setPkey = EVP_PKEY_new_raw_private_key_ex(wpLibCtx, "X25519", NULL, + privKey, sizeof(privKey)); + if (setPkey == NULL) { + PRINT_ERR_MSG("Failed to import X25519 private key"); + err = 1; + } + } + if (err == 0) { + if (EVP_PKEY_set1_encoded_public_key(setPkey, buf, shortLen) == 1) { + PRINT_ERR_MSG("X25519 set1_encoded_public_key accepted short key"); + err = 1; + } + } + + EVP_PKEY_free(setPkey); + EVP_PKEY_free(pkey); + EVP_PKEY_CTX_free(ctx); + OPENSSL_free(buf); + return err; +} #endif /* WP_HAVE_X25519 */ int test_ecx_dup(void *data) diff --git a/test/unit.c b/test/unit.c index 334e3623..bd5ed3bc 100644 --- a/test/unit.c +++ b/test/unit.c @@ -530,6 +530,7 @@ TEST_CASE test_case[] = { TEST_DECL(test_ecx_x25519_raw_priv_roundtrip, NULL), TEST_DECL(test_ecx_x25519_get_params_stale_ret, NULL), TEST_DECL(test_ecx_import_zero_priv, NULL), + TEST_DECL(test_ecx_import_short_pub, NULL), #endif TEST_DECL(test_ecx_dup, NULL), #endif diff --git a/test/unit.h b/test/unit.h index c5ebb0d2..68ff7ae5 100644 --- a/test/unit.h +++ b/test/unit.h @@ -546,6 +546,7 @@ int test_ecx_null_init(void *data); int test_ecx_x25519_raw_priv_roundtrip(void *data); int test_ecx_x25519_get_params_stale_ret(void *data); int test_ecx_import_zero_priv(void *data); +int test_ecx_import_short_pub(void *data); #endif /* WP_HAVE_X25519 */ int test_ecx_dup(void *data); #endif