From 29f3b306518534b435da884e130f30559a2fac9b Mon Sep 17 00:00:00 2001 From: Eric Blankenhorn Date: Fri, 8 May 2026 16:53:17 -0500 Subject: [PATCH 1/2] Fix in ECC point conversion --- src/pk_ec.c | 22 +++++++++++++-- tests/api/test_ossl_ec.c | 59 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/src/pk_ec.c b/src/pk_ec.c index faed7f20a67..9d83d90379d 100644 --- a/src/pk_ec.c +++ b/src/pk_ec.c @@ -1356,9 +1356,19 @@ WOLFSSL_EC_POINT* wolfSSL_EC_POINT_hex2point(const WOLFSSL_EC_GROUP *group, } key_sz = (wolfSSL_EC_GROUP_get_degree(group) + 7) / 8; + if (key_sz <= 0 || (size_t)key_sz > MAX_ECC_BYTES) + goto err; + if (hex[0] == '0' && hex[1] == '4') { /* uncompressed mode */ str_sz = (size_t)key_sz * 2; + /* The uncompressed encoding is exactly 2 + 4*key_sz hex chars + * ("04" prefix plus X and Y as 2*key_sz hex chars each). Reject + * any other length so XMEMCPY/BN_hex2bn cannot read past the end + * of the input and trailing garbage is not silently absorbed. */ + if (XSTRLEN(hex + 2) != str_sz * 2) + goto err; + XMEMSET(strGx, 0x0, str_sz + 1); XMEMCPY(strGx, hex + 2, str_sz); @@ -1378,8 +1388,16 @@ WOLFSSL_EC_POINT* wolfSSL_EC_POINT_hex2point(const WOLFSSL_EC_GROUP *group, } else if (hex[0] == '0' && (hex[1] == '2' || hex[1] == '3')) { size_t sz = XSTRLEN(hex + 2) / 2; - /* compressed mode */ - octGx[0] = ECC_POINT_COMP_ODD; + /* The SEC 1 compressed encoding is exactly 1 + key_sz bytes, so + * the hex payload after the "02"/"03" prefix is exactly 2*key_sz + * hex chars. Require an exact match: rejecting sz > key_sz keeps + * hex_to_bytes() from writing past strGx, and rejecting sz < + * key_sz keeps wolfSSL_ECPoint_d2i() from reading uninitialized + * stack bytes for the X coordinate. */ + if (sz != (size_t)key_sz) + goto err; + octGx[0] = (hex[1] == '2') ? ECC_POINT_COMP_EVEN + : ECC_POINT_COMP_ODD; if (hex_to_bytes(hex + 2, octGx + 1, sz) != sz) { goto err; } diff --git a/tests/api/test_ossl_ec.c b/tests/api/test_ossl_ec.c index 2423f4e9f49..970533179c4 100644 --- a/tests/api/test_ossl_ec.c +++ b/tests/api/test_ossl_ec.c @@ -631,6 +631,65 @@ int test_wolfSSL_EC_POINT(void) #endif XFREE(hexStr, NULL, DYNAMIC_TYPE_ECC); EC_POINT_free(get_point); + get_point = NULL; + + /* Regression: oversized compressed-point hex must not overflow the stack + * buffer in wolfSSL_EC_POINT_hex2point(). The byte length decoded from + * the hex string must be bounded by the curve's ordinate size. */ + { + char tooLongHex[2 + 600 + 1]; + size_t i; + + tooLongHex[0] = '0'; + tooLongHex[1] = '3'; + for (i = 2; i < sizeof(tooLongHex) - 1; i++) + tooLongHex[i] = 'A'; + tooLongHex[sizeof(tooLongHex) - 1] = '\0'; + ExpectNull(EC_POINT_hex2point(group, tooLongHex, NULL, ctx)); + + /* Same with the "02" (even Y) prefix. */ + tooLongHex[1] = '2'; + ExpectNull(EC_POINT_hex2point(group, tooLongHex, NULL, ctx)); + + /* Truncated uncompressed input: prefix "04" with too few hex chars + * to cover the curve's coordinates. Must return NULL without + * reading past the end of the input string. */ + ExpectNull(EC_POINT_hex2point(group, "04AB", NULL, ctx)); + + /* Empty payload after a recognized prefix. */ + ExpectNull(EC_POINT_hex2point(group, "03", NULL, ctx)); + ExpectNull(EC_POINT_hex2point(group, "04", NULL, ctx)); + + /* Partially populated compressed input: must be rejected so that + * wolfSSL_ECPoint_d2i() does not consume uninitialized stack + * bytes as the X coordinate. */ + ExpectNull(EC_POINT_hex2point(group, "03AB", NULL, ctx)); + ExpectNull(EC_POINT_hex2point(group, "02ABCD", NULL, ctx)); + } + + #if defined(HAVE_COMP_KEY) && !defined(HAVE_SELFTEST) + /* Round-trip a compressed point with even Y ("02" prefix) to verify + * that the prefix-to-parity flag is honored in the compressed branch. */ + { + EC_POINT* even_point = NULL; + EC_POINT* round_trip = NULL; + char* even_hex = NULL; + + ExpectNotNull(even_point = EC_POINT_dup(Gxy, group)); + ExpectIntEQ(EC_POINT_invert(group, even_point, ctx), 1); + ExpectNotNull(even_hex = EC_POINT_point2hex(group, even_point, + POINT_CONVERSION_COMPRESSED, ctx)); + /* P-256 G has odd Y; inverting flips Y parity so prefix is "02". */ + ExpectIntEQ(even_hex[1], '2'); + ExpectNotNull(round_trip = EC_POINT_hex2point(group, even_hex, NULL, + ctx)); + ExpectIntEQ(EC_POINT_cmp(group, even_point, round_trip, ctx), 0); + + XFREE(even_hex, NULL, DYNAMIC_TYPE_ECC); + EC_POINT_free(round_trip); + EC_POINT_free(even_point); + } + #endif #ifndef HAVE_SELFTEST /* Test point to oct */ From 05d73707ef5554a8f644fd9d3ddfa9d80c275a35 Mon Sep 17 00:00:00 2001 From: Eric Blankenhorn Date: Fri, 8 May 2026 17:15:06 -0500 Subject: [PATCH 2/2] Fixes from review --- src/pk_ec.c | 18 ++++++++++-------- tests/api/test_ossl_ec.c | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/pk_ec.c b/src/pk_ec.c index 9d83d90379d..4b3b49e303d 100644 --- a/src/pk_ec.c +++ b/src/pk_ec.c @@ -1387,18 +1387,20 @@ WOLFSSL_EC_POINT* wolfSSL_EC_POINT_hex2point(const WOLFSSL_EC_GROUP *group, } } else if (hex[0] == '0' && (hex[1] == '2' || hex[1] == '3')) { - size_t sz = XSTRLEN(hex + 2) / 2; /* The SEC 1 compressed encoding is exactly 1 + key_sz bytes, so - * the hex payload after the "02"/"03" prefix is exactly 2*key_sz - * hex chars. Require an exact match: rejecting sz > key_sz keeps - * hex_to_bytes() from writing past strGx, and rejecting sz < - * key_sz keeps wolfSSL_ECPoint_d2i() from reading uninitialized - * stack bytes for the X coordinate. */ - if (sz != (size_t)key_sz) + * the hex payload after the "02"/"03" prefix must be exactly + * 2*key_sz hex chars. Compare the input length directly (rather + * than XSTRLEN/2) so that odd-length inputs cannot slip past via + * integer truncation. The exact-match rejects oversized inputs + * (preventing a hex_to_bytes() write past strGx) and undersized + * inputs (preventing wolfSSL_ECPoint_d2i() from reading + * uninitialized stack bytes as the X coordinate). */ + if (XSTRLEN(hex + 2) != (size_t)key_sz * 2) goto err; octGx[0] = (hex[1] == '2') ? ECC_POINT_COMP_EVEN : ECC_POINT_COMP_ODD; - if (hex_to_bytes(hex + 2, octGx + 1, sz) != sz) { + if (hex_to_bytes(hex + 2, octGx + 1, (size_t)key_sz) + != (size_t)key_sz) { goto err; } if (wolfSSL_ECPoint_d2i(octGx, (word32)key_sz + 1, group, p) diff --git a/tests/api/test_ossl_ec.c b/tests/api/test_ossl_ec.c index 970533179c4..d028eb1f9ff 100644 --- a/tests/api/test_ossl_ec.c +++ b/tests/api/test_ossl_ec.c @@ -665,6 +665,20 @@ int test_wolfSSL_EC_POINT(void) * bytes as the X coordinate. */ ExpectNull(EC_POINT_hex2point(group, "03AB", NULL, ctx)); ExpectNull(EC_POINT_hex2point(group, "02ABCD", NULL, ctx)); + + /* Odd-length compressed payload: 2*key_sz + 1 hex chars after + * the "03" prefix (P-256: 65 chars). A truncating-divide bound + * (sz = XSTRLEN/2) would round down to key_sz and accept this; + * an exact-length compare must reject it. */ + { + char oddLenHex[2 + 65 + 1]; + for (i = 2; i < sizeof(oddLenHex) - 1; i++) + oddLenHex[i] = 'A'; + oddLenHex[0] = '0'; + oddLenHex[1] = '3'; + oddLenHex[sizeof(oddLenHex) - 1] = '\0'; + ExpectNull(EC_POINT_hex2point(group, oddLenHex, NULL, ctx)); + } } #if defined(HAVE_COMP_KEY) && !defined(HAVE_SELFTEST)