diff --git a/src/pk_ec.c b/src/pk_ec.c index faed7f20a6..4b3b49e303 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); @@ -1377,10 +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; - /* compressed mode */ - octGx[0] = ECC_POINT_COMP_ODD; - if (hex_to_bytes(hex + 2, octGx + 1, sz) != sz) { + /* The SEC 1 compressed encoding is exactly 1 + key_sz bytes, so + * 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, (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 2423f4e9f4..d028eb1f9f 100644 --- a/tests/api/test_ossl_ec.c +++ b/tests/api/test_ossl_ec.c @@ -631,6 +631,79 @@ 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)); + + /* 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) + /* 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 */