From 899aeadd7ce01df5fa39792ef4842aa929e36fd7 Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Fri, 26 Jun 2026 18:32:32 +0800 Subject: [PATCH 01/11] fix(bootloader_support): guard NULL efuse digest slot in secure-boot verify --- .../src/secure_boot_v2/secure_boot_signatures_app.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/components/bootloader_support/src/secure_boot_v2/secure_boot_signatures_app.c b/components/bootloader_support/src/secure_boot_v2/secure_boot_signatures_app.c index f8dc62b2db5..62b029eb373 100644 --- a/components/bootloader_support/src/secure_boot_v2/secure_boot_signatures_app.c +++ b/components/bootloader_support/src/secure_boot_v2/secure_boot_signatures_app.c @@ -331,7 +331,13 @@ esp_err_t esp_secure_boot_verify_with_efuse_digest_index(int efuse_digest_index, // Read key digests from efuse esp_secure_boot_key_digests_t efuse_key_digests; memset(&efuse_key_digests, 0, sizeof(esp_secure_boot_key_digests_t)); - esp_secure_boot_read_key_digests(&efuse_key_digests); + /* A non-revoked slot can still be unprovisioned, leaving its digest pointer NULL even + * when the read returns ESP_OK; comparing it would dereference NULL in memcmp(). Check + * both the read result and the specific slot (matches get_secure_boot_key_digests()). */ + if (esp_secure_boot_read_key_digests(&efuse_key_digests) != ESP_OK || + efuse_key_digests.key_digests[efuse_digest_index] == NULL) { + return ESP_FAIL; + } for (int i = 0; i < img_key_digests.num_digests; i++) { if (!memcmp(img_key_digests.key_digests[i], efuse_key_digests.key_digests[efuse_digest_index], ESP_SECURE_BOOT_KEY_DIGEST_LEN)) { From 1fc6094b144c2d3a47dfce89a15708dae7857e7e Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Fri, 26 Jun 2026 18:41:16 +0800 Subject: [PATCH 02/11] fix(hal): clamp tag_len in aes_hal_gcm_read_tag to prevent OOB --- components/hal/aes_hal.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/components/hal/aes_hal.c b/components/hal/aes_hal.c index f70ed1b9875..a8358d0a59f 100644 --- a/components/hal/aes_hal.c +++ b/components/hal/aes_hal.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -126,6 +126,11 @@ void aes_hal_gcm_read_tag(uint8_t *tag, size_t tag_len) { uint8_t tag_res[TAG_BYTES]; aes_ll_gcm_read_tag(tag_res); + /* The GCM tag is at most TAG_BYTES (16). Clamp the caller-supplied length so an oversized + * tag_len cannot over-read tag_res or over-write the caller's tag buffer (CWE-125). */ + if (tag_len > TAG_BYTES) { + tag_len = TAG_BYTES; + } memcpy(tag, tag_res, tag_len); } From ef8e91525866c92903084b996eeab957b4415033 Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Fri, 26 Jun 2026 18:44:49 +0800 Subject: [PATCH 03/11] fix(esp_https_server): free TLS session on transport_ctx OOM in httpd_ssl_open --- components/esp_https_server/src/https_server.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/esp_https_server/src/https_server.c b/components/esp_https_server/src/https_server.c index 7f4e083ac30..e20e6994137 100644 --- a/components/esp_https_server/src/https_server.c +++ b/components/esp_https_server/src/https_server.c @@ -189,6 +189,10 @@ static esp_err_t httpd_ssl_open(httpd_handle_t server, int sockfd) esp_https_server_last_error_t last_error = {0}; last_error.last_error = ESP_ERR_NO_MEM; http_dispatch_event_to_event_loop(HTTPS_SERVER_EVENT_ERROR, &last_error, sizeof(last_error)); + /* The TLS session (and its underlying socket fd) is already established; free it + * before returning so a failed connection under memory pressure does not leak the + * SSL context and the socket (CWE-401 / CWE-772). */ + esp_tls_server_session_delete(tls); return ESP_ERR_NO_MEM; } transport_ctx->tls = tls; From 7bf19e2f0e23e8f8ab18e70bda87636d7aa5050e Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Fri, 26 Jun 2026 19:28:20 +0800 Subject: [PATCH 04/11] fix(esp-tls): reject NULL host/url in plain-TCP and async HTTP connect --- components/esp-tls/esp_tls.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/components/esp-tls/esp_tls.c b/components/esp-tls/esp_tls.c index 4b46b32dd31..3361205f75d 100644 --- a/components/esp-tls/esp_tls.c +++ b/components/esp-tls/esp_tls.c @@ -567,7 +567,7 @@ static int esp_tls_low_level_conn(const char *hostname, int hostlen, int port, c */ esp_err_t esp_tls_plain_tcp_connect(const char *host, int hostlen, int port, const esp_tls_cfg_t *cfg, esp_tls_error_handle_t error_handle, int *sockfd) { - if (sockfd == NULL || error_handle == NULL) { + if (sockfd == NULL || error_handle == NULL || host == NULL || hostlen < 0) { return ESP_ERR_INVALID_ARG; } return tcp_connect(host, hostlen, port, cfg, error_handle, sockfd); @@ -671,6 +671,10 @@ int esp_tls_conn_http_new_sync(const char *url, const esp_tls_cfg_t *cfg, esp_tl */ int esp_tls_conn_http_new_async(const char *url, const esp_tls_cfg_t *cfg, esp_tls_t *tls) { + if (!url || !cfg || !tls) { + return -1; + } + /* Parse URI */ struct http_parser_url u; http_parser_url_init(&u); From 7c2e4b2a58c4e1c37f102a457acb4b251c1f5c14 Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Mon, 6 Jul 2026 17:14:45 +0800 Subject: [PATCH 05/11] fix(app_update): close partition-table OOB read and rollback-guard gap --- components/app_update/esp_ota_ops.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/components/app_update/esp_ota_ops.c b/components/app_update/esp_ota_ops.c index f77d54b5459..4d50ef3d60c 100644 --- a/components/app_update/esp_ota_ops.c +++ b/components/app_update/esp_ota_ops.c @@ -238,6 +238,21 @@ esp_err_t esp_ota_resume(const esp_partition_t *partition, const size_t erase_si return ESP_ERR_OTA_PARTITION_CONFLICT; } +#ifdef CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE + // Mirror esp_ota_begin(): refuse to resume an OTA into an app slot while the running + // app is still pending verification, otherwise the rollback target could be + // overwritten during the unconfirmed window. + if (partition->type == ESP_PARTITION_TYPE_APP) { + esp_ota_img_states_t ota_state_running_part; + if (esp_ota_get_state_partition(running_partition, &ota_state_running_part) == ESP_OK) { + if (ota_state_running_part == ESP_OTA_IMG_PENDING_VERIFY) { + ESP_LOGE(TAG, "Running app has not confirmed state (ESP_OTA_IMG_PENDING_VERIFY)"); + return ESP_ERR_OTA_ROLLBACK_INVALID_STATE; + } + } + } +#endif + new_entry = esp_ota_init_entry(partition); if (new_entry == NULL) { return ESP_ERR_NO_MEM; @@ -335,7 +350,10 @@ esp_err_t esp_ota_write(esp_ota_handle_t handle, const void *data, size_t size) } } else if (it->partition.final->type == ESP_PARTITION_TYPE_PARTITION_TABLE) { - if (*(uint16_t*)data_bytes != (uint16_t)ESP_PARTITION_MAGIC) { + /* Read the 2-byte magic word only if the caller-supplied buffer is large + * enough; otherwise this would read past a short (e.g. 1-byte) first chunk. + * A too-short chunk is still fully validated later by esp_partition_table_verify(). */ + if (size >= sizeof(uint16_t) && *(uint16_t*)data_bytes != (uint16_t)ESP_PARTITION_MAGIC) { ESP_LOGE(TAG, "Partition table image has invalid magic word (expected 0x50AA, saw 0x%04x)", *(uint16_t*)data_bytes); return ESP_ERR_OTA_VALIDATE_FAILED; } From 370cbcaff94bb1be5065ccb4fd178d0d55b806c9 Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Mon, 6 Jul 2026 17:16:31 +0800 Subject: [PATCH 06/11] fix(esp_http_server): close UAF/double-free and query/cookie buffer underflows --- components/esp_http_server/src/httpd_parse.c | 13 ++++++++++--- components/esp_http_server/src/httpd_uri.c | 2 ++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/components/esp_http_server/src/httpd_parse.c b/components/esp_http_server/src/httpd_parse.c index 76fbe8a2cdf..ad592ba8074 100644 --- a/components/esp_http_server/src/httpd_parse.c +++ b/components/esp_http_server/src/httpd_parse.c @@ -888,7 +888,9 @@ bool httpd_validate_req_ptr(httpd_req_t *r) /* Helper function to get a URL query tag from a query string of the type param1=val1¶m2=val2 */ esp_err_t httpd_query_key_value(const char *qry_str, const char *key, char *val, size_t val_size) { - if (qry_str == NULL || key == NULL || val == NULL) { + /* Reject a zero-size output buffer: val_size - 1 below would underflow to SIZE_MAX, + * defeating the truncation check and overflowing the caller's buffer (CWE-191/CWE-787). */ + if (qry_str == NULL || key == NULL || val == NULL || val_size == 0) { return ESP_ERR_INVALID_ARG; } @@ -971,7 +973,9 @@ size_t httpd_req_get_url_query_len(httpd_req_t *r) esp_err_t httpd_req_get_url_query_str(httpd_req_t *r, char *buf, size_t buf_len) { - if (r == NULL || buf == NULL) { + /* Reject a zero-size output buffer: buf_len - 1 below would underflow to SIZE_MAX, + * defeating the truncation check and overflowing the caller's buffer (CWE-191/CWE-787). */ + if (r == NULL || buf == NULL || buf_len == 0) { return ESP_ERR_INVALID_ARG; } @@ -1130,7 +1134,10 @@ esp_err_t httpd_req_get_hdr_value_str(httpd_req_t *r, const char *field, char *v /* Helper function to get a cookie value from a cookie string of the type "cookie1=val1; cookie2=val2" */ esp_err_t static httpd_cookie_key_value(const char *cookie_str, const char *key, char *val, size_t *val_size) { - if (cookie_str == NULL || key == NULL || val == NULL) { + /* Reject a NULL or zero-size output buffer: *val_size - 1 below would underflow to + * SIZE_MAX, defeating the truncation check and overflowing the caller's buffer + * (CWE-191/CWE-787). val_size is also dereferenced below, so it must be non-NULL. */ + if (cookie_str == NULL || key == NULL || val == NULL || val_size == NULL || *val_size == 0) { return ESP_ERR_INVALID_ARG; } diff --git a/components/esp_http_server/src/httpd_uri.c b/components/esp_http_server/src/httpd_uri.c index 006538e82c9..75b45561436 100644 --- a/components/esp_http_server/src/httpd_uri.c +++ b/components/esp_http_server/src/httpd_uri.c @@ -159,6 +159,7 @@ esp_err_t httpd_register_uri_handler(httpd_handle_t handle, if (hd->hd_calls[i]->uri == NULL) { /* Failed to allocate memory */ free(hd->hd_calls[i]); + hd->hd_calls[i] = NULL; return ESP_ERR_HTTPD_ALLOC_MEM; } @@ -181,6 +182,7 @@ esp_err_t httpd_register_uri_handler(httpd_handle_t handle, /* Failed to allocate memory */ free((void *)hd->hd_calls[i]->uri); free(hd->hd_calls[i]); + hd->hd_calls[i] = NULL; return ESP_ERR_HTTPD_ALLOC_MEM; } } else { From cd516819ae7c7510f3f59c4be6005c31b0d4e736 Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Mon, 6 Jul 2026 17:18:11 +0800 Subject: [PATCH 07/11] fix(esp_http_client): fix digest-auth leaks and credential/handle use-after-free --- components/esp_http_client/esp_http_client.c | 26 +++++++++++++++++--- components/esp_http_client/lib/http_auth.c | 9 ++++++- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/components/esp_http_client/esp_http_client.c b/components/esp_http_client/esp_http_client.c index 16a56686d70..f34e22585b1 100644 --- a/components/esp_http_client/esp_http_client.c +++ b/components/esp_http_client/esp_http_client.c @@ -410,10 +410,17 @@ esp_err_t esp_http_client_set_username(esp_http_client_handle_t client, const ch ESP_LOGE(TAG, "client must not be NULL"); return ESP_ERR_INVALID_ARG; } + /* Duplicate first so that passing the current username back in (e.g. the pointer + * returned by esp_http_client_get_username()) is safe: the old buffer is freed only + * after the copy succeeds, avoiding a use-after-free on self-aliasing (CWE-416). */ + char *new_username = username ? strdup(username) : NULL; + if (username != NULL && new_username == NULL) { + return ESP_ERR_NO_MEM; + } if (client->connection_info.username != NULL) { free(client->connection_info.username); } - client->connection_info.username = username ? strdup(username) : NULL; + client->connection_info.username = new_username; return ESP_OK; } @@ -456,11 +463,19 @@ esp_err_t esp_http_client_set_password(esp_http_client_handle_t client, const ch ESP_LOGE(TAG, "client must not be NULL"); return ESP_ERR_INVALID_ARG; } + /* Duplicate first so that passing the current password back in (e.g. the pointer + * returned by esp_http_client_get_password()) is safe: zeroize and free the old buffer + * only after the copy succeeds, avoiding a use-after-free / credential corruption + * (CWE-416) caused by memset zeroing the source before strdup reads it. */ + char *new_password = password ? strdup(password) : NULL; + if (password != NULL && new_password == NULL) { + return ESP_ERR_NO_MEM; + } if (client->connection_info.password != NULL) { memset(client->connection_info.password, 0, strlen(client->connection_info.password)); free(client->connection_info.password); } - client->connection_info.password = password ? strdup(password) : NULL; + client->connection_info.password = new_password; return ESP_OK; } @@ -1645,14 +1660,17 @@ static esp_err_t esp_http_client_connect(esp_http_client_handle_t client) } } client->state = HTTP_STATE_CONNECTED; - http_dispatch_event(client, HTTP_EVENT_ON_CONNECTED, NULL, 0); - http_dispatch_event_to_event_loop(HTTP_EVENT_ON_CONNECTED, &client, sizeof(esp_http_client_handle_t)); #ifdef CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS + /* Perform handle-dependent session-ticket bookkeeping before dispatching the user + * callback: a synchronous HTTP_EVENT_ON_CONNECTED handler is permitted to destroy + * the client, so dereferencing the handle afterwards would be a UAF (CWE-416). */ if (client->session_ticket_state != SESSION_TICKET_UNUSED) { esp_transport_ssl_session_ticket_operation(client->transport, ESP_TRANSPORT_SESSION_TICKET_SAVE); client->session_ticket_state = SESSION_TICKET_SAVED; } #endif + http_dispatch_event(client, HTTP_EVENT_ON_CONNECTED, NULL, 0); + http_dispatch_event_to_event_loop(HTTP_EVENT_ON_CONNECTED, &client, sizeof(esp_http_client_handle_t)); } return ESP_OK; diff --git a/components/esp_http_client/lib/http_auth.c b/components/esp_http_client/lib/http_auth.c index 8de6f0c173a..a04f6b12cfa 100644 --- a/components/esp_http_client/lib/http_auth.c +++ b/components/esp_http_client/lib/http_auth.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -194,11 +194,18 @@ char *http_auth_digest(const char *username, const char *password, esp_http_auth if (rc < 0) { ESP_LOGE(TAG, "asprintf() returned: %d", rc); ret = ESP_FAIL; + free(auth_str); + auth_str = NULL; goto _digest_exit; } + /* http_utils_append_string() realloc()s auth_str; on failure it returns NULL + * without freeing the original buffer, so keep a handle to free it here. */ + char *prev_auth_str = auth_str; auth_str = http_utils_append_string(&auth_str, temp_auth_str, strlen(temp_auth_str)); if (!auth_str) { ret = ESP_FAIL; + free(prev_auth_str); + free(temp_auth_str); goto _digest_exit; } free(temp_auth_str); From 7ca54ff43aa01276295d16badd4291afa65a2f03 Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Mon, 6 Jul 2026 17:24:57 +0800 Subject: [PATCH 08/11] fix(mbedtls): validate crypto input lengths (TEE OOB, auth-bypass, overflows) --- .../mbedtls/esp_crt_bundle/esp_crt_bundle.c | 22 ++++++++++++++-- components/mbedtls/port/aes/block/esp_aes.c | 23 ++++++++++++++++- components/mbedtls/port/aes/dma/esp_aes.c | 19 ++++++++++++++ components/mbedtls/port/aes/esp_aes_gcm.c | 24 +++++++++++++++++- components/mbedtls/port/ecc/esp_ecc.c | 25 ++++++++++++++++++- 5 files changed, 108 insertions(+), 5 deletions(-) diff --git a/components/mbedtls/esp_crt_bundle/esp_crt_bundle.c b/components/mbedtls/esp_crt_bundle/esp_crt_bundle.c index de6b7f71073..a29b0226b4a 100644 --- a/components/mbedtls/esp_crt_bundle/esp_crt_bundle.c +++ b/components/mbedtls/esp_crt_bundle/esp_crt_bundle.c @@ -99,9 +99,11 @@ static const uint8_t* esp_crt_get_key(const cert_t cert) return esp_crt_get_name(cert) + esp_crt_get_name_len(cert); } -static uint16_t esp_crt_get_len(const cert_t cert) +static uint32_t esp_crt_get_len(const cert_t cert) { - return CRT_HEADER_SIZE + esp_crt_get_name_len(cert) + esp_crt_get_key_len(cert); + /* Widened to uint32_t: name_len and key_len are each uint16_t, so their sum plus the + * header can exceed UINT16_MAX and would otherwise wrap, under-reporting the cert size. */ + return (uint32_t)CRT_HEADER_SIZE + (uint32_t)esp_crt_get_name_len(cert) + (uint32_t)esp_crt_get_key_len(cert); } static uint32_t esp_crt_get_cert_offset(const bundle_t bundle, const uint32_t index) @@ -305,6 +307,11 @@ static bool esp_crt_check_bundle(const uint8_t* const x509_bundle, const size_t return false; } + if (unlikely(num_certs == 0)) { + // No certificates: the loops below compute num_certs - 1, which would underflow. + return false; + } + // Check all offsets for consistency with certificate data for (uint32_t i = 0; i < num_certs - 1; ++i) { const uint32_t off = esp_crt_get_cert_offset(x509_bundle, i); @@ -318,6 +325,17 @@ static bool esp_crt_check_bundle(const uint8_t* const x509_bundle, const size_t } } + // The loop above stops at num_certs - 1, so the final certificate's extent is never + // validated; check it explicitly so its key data cannot run past the bundle (CWE-125). + const uint32_t last_off = esp_crt_get_cert_offset(x509_bundle, num_certs - 1); + if (unlikely(last_off >= bundle_size)) { + return false; + } + const uint32_t last_len = esp_crt_get_len(x509_bundle + last_off); + if (unlikely((uint64_t)last_off + last_len > bundle_size)) { + return false; + } + // All checks passed. return true; } diff --git a/components/mbedtls/port/aes/block/esp_aes.c b/components/mbedtls/port/aes/block/esp_aes.c index 00cd9584381..4de7ff666ba 100644 --- a/components/mbedtls/port/aes/block/esp_aes.c +++ b/components/mbedtls/port/aes/block/esp_aes.c @@ -358,6 +358,16 @@ int esp_aes_crypt_cfb128(esp_aes_context *ctx, int c; size_t n = *iv_off; + + /* iv[] is a fixed AES_BLOCK_BYTES buffer and n indexes it directly (before the modulo update), + * so a caller-supplied *iv_off >= AES_BLOCK_BYTES -- attacker-controlled via the TEE secure + * service -- is an out-of-bounds read/write of iv[] in TEE context (CWE-787 / CWE-125). Bound + * it here (before acquiring the AES hardware) matching esp_aes_crypt_ofb(). */ + if (n >= AES_BLOCK_BYTES) { + ESP_LOGE(TAG, "IV offset out of bounds"); + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + esp_aes_acquire_hardware(); ctx->key_in_hardware = 0; ctx->key_in_hardware = aes_hal_setkey(ctx->key, ctx->key_bytes, ESP_AES_ENCRYPT); @@ -491,6 +501,17 @@ int esp_aes_crypt_ctr(esp_aes_context *ctx, } size_t n = *nc_off; + + /* stream_block[] is a fixed AES_BLOCK_BYTES buffer and n indexes it directly (before the + * modulo update), so a caller-supplied *nc_off >= AES_BLOCK_BYTES -- attacker-controlled via + * the TEE secure service -- is an out-of-bounds read of stream_block[] in TEE context + * (CWE-125), leaking adjacent memory into the output. Bound it here, matching + * esp_aes_crypt_cfb128() and esp_aes_crypt_ofb(). */ + if (n >= AES_BLOCK_BYTES) { + ESP_LOGE(TAG, "Nonce offset out of bounds"); + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + if (!valid_key_length(ctx)) { return MBEDTLS_ERR_AES_INVALID_KEY_LENGTH; } @@ -556,7 +577,7 @@ int esp_aes_crypt_ofb(esp_aes_context *ctx, n = *iv_off; - if (n > 15) { + if (n >= AES_BLOCK_BYTES) { return (MBEDTLS_ERR_AES_BAD_INPUT_DATA); } diff --git a/components/mbedtls/port/aes/dma/esp_aes.c b/components/mbedtls/port/aes/dma/esp_aes.c index 696168a9c35..fcd4ed8a203 100644 --- a/components/mbedtls/port/aes/dma/esp_aes.c +++ b/components/mbedtls/port/aes/dma/esp_aes.c @@ -347,6 +347,15 @@ int esp_aes_crypt_cfb128(esp_aes_context *ctx, n = *iv_off; + /* iv[] is a fixed AES_BLOCK_BYTES buffer and n indexes it directly (before the modulo update), + * so a caller-supplied *iv_off >= AES_BLOCK_BYTES -- attacker-controlled via the TEE secure + * service -- is an out-of-bounds read/write of iv[] in TEE context (CWE-787 / CWE-125). Bound + * it here. */ + if (n >= AES_BLOCK_BYTES) { + ESP_LOGE(TAG, "IV offset out of bounds"); + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + /* First process the *iv_off bytes * which are pending from the previous call to this API */ @@ -492,6 +501,16 @@ int esp_aes_crypt_ctr(esp_aes_context *ctx, n = *nc_off; + /* stream_block[] is a fixed AES_BLOCK_BYTES buffer and n indexes it directly (before the + * modulo update), so a caller-supplied *nc_off >= AES_BLOCK_BYTES -- attacker-controlled via + * the TEE secure service -- is an out-of-bounds read of stream_block[] in TEE context + * (CWE-125), leaking adjacent memory into the output. Bound it here, matching + * esp_aes_crypt_cfb128() and esp_aes_crypt_ofb(). */ + if (n >= AES_BLOCK_BYTES) { + ESP_LOGE(TAG, "Nonce offset out of bounds"); + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + if (!valid_key_length(ctx)) { return MBEDTLS_ERR_AES_INVALID_KEY_LENGTH; } diff --git a/components/mbedtls/port/aes/esp_aes_gcm.c b/components/mbedtls/port/aes/esp_aes_gcm.c index 839fb71a1fb..9e5ac70aa04 100644 --- a/components/mbedtls/port/aes/esp_aes_gcm.c +++ b/components/mbedtls/port/aes/esp_aes_gcm.c @@ -518,6 +518,14 @@ int esp_aes_gcm_update( esp_gcm_context *ctx, return MBEDTLS_ERR_GCM_BAD_INPUT; } + /* Honor the documented contract: the output buffer must hold input_length bytes, which are + * written unconditionally below; without this check an undersized buffer overflows (CWE-20 + * -> CWE-787). MBEDTLS_ERR_GCM_BAD_INPUT is the bad-input error code used throughout this file. */ + if ( output_size < input_length ) { + ESP_LOGE(TAG, "Output buffer too small"); + return MBEDTLS_ERR_GCM_BAD_INPUT; + } + if ( output > input && (size_t) ( output - input ) < input_length ) { return ( MBEDTLS_ERR_GCM_BAD_INPUT ); } @@ -638,7 +646,7 @@ static int esp_aes_gcm_crypt_and_tag_partial_hw( esp_gcm_context *ctx, return ( ret ); } - if ( ( ret = esp_aes_gcm_update( ctx, input, length, output, 0, &olen ) ) != 0 ) { + if ( ( ret = esp_aes_gcm_update( ctx, input, length, output, length, &olen ) ) != 0 ) { return ( ret ); } @@ -671,6 +679,12 @@ int esp_aes_gcm_crypt_and_tag( esp_gcm_context *ctx, return mbedtls_gcm_crypt_and_tag_soft(ctx->ctx_soft, mode, length, iv, iv_len, aad, aad_len, input, output, tag_len, tag); } #endif + /* GCM tags are 4..16 bytes. Validate here so the hardware path also rejects an invalid + * tag_len (the software path enforces this in esp_aes_gcm_finish()); otherwise the HAL tag + * read would be driven with an out-of-range length (CWE-125 / CWE-787). */ + if ( tag_len < 4 || tag_len > 16 ) { + return MBEDTLS_ERR_GCM_BAD_INPUT; + } #if CONFIG_MBEDTLS_HARDWARE_GCM int ret; size_t remainder_bit; @@ -771,6 +785,14 @@ int esp_aes_gcm_auth_decrypt( esp_gcm_context *ctx, size_t i; int diff; + /* Validate tag_len before use: a zero tag_len makes the constant-time comparison loop + * below run zero iterations, so diff stays 0 and any forged ciphertext is accepted as + * authentic (CWE-347); an oversized tag_len also over-reads the 16-byte check_tag + * (CWE-125). Enforce the same 4..16 range as esp_aes_gcm_finish(). */ + if ( tag_len > 16 || tag_len < 4 ) { + return MBEDTLS_ERR_GCM_BAD_INPUT; + } + if ( ( ret = esp_aes_gcm_crypt_and_tag( ctx, ESP_AES_DECRYPT, length, iv, iv_len, aad, aad_len, input, output, tag_len, check_tag ) ) != 0 ) { diff --git a/components/mbedtls/port/ecc/esp_ecc.c b/components/mbedtls/port/ecc/esp_ecc.c index 43060022fe5..59a558fd2e7 100644 --- a/components/mbedtls/port/ecc/esp_ecc.c +++ b/components/mbedtls/port/ecc/esp_ecc.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -33,6 +33,17 @@ int esp_ecc_point_multiply(const ecc_point_t *point, const uint8_t *scalar, ecc_ uint16_t len = point->len; ecc_mode_t work_mode = verify_first ? ECC_MODE_VERIFY_THEN_POINT_MUL : ECC_MODE_POINT_MUL; + /* len is used as the HW read/write byte count for the fixed-size ecc_point_t buffers; + * reject any value that is not a supported curve length before touching hardware. On the + * TEE secure-service path this field is attacker-controlled (CWE-20 -> OOB read/write). */ + if (len != P192_LEN && len != P256_LEN +#if SOC_ECC_SUPPORT_CURVE_P384 + && len != P384_LEN +#endif + ) { + return -1; + } + esp_ecc_acquire_hardware(); ecc_hal_write_mul_param(scalar, point->x, point->y, len); @@ -65,6 +76,18 @@ int esp_ecc_point_verify(const ecc_point_t *point) { int result; + /* point->len drives a fixed-stride MMIO write loop in the HAL; an unvalidated oversized + * value (attacker-controlled via the TEE secure service) walks past the ECC register block + * and can reach other peripheral registers (CWE-787). Reject non-curve lengths up front and + * return 0 (point not verified) -- the fail-safe value for this routine. */ + if (point->len != P192_LEN && point->len != P256_LEN +#if SOC_ECC_SUPPORT_CURVE_P384 + && point->len != P384_LEN +#endif + ) { + return 0; + } + esp_ecc_acquire_hardware(); ecc_hal_write_verify_param(point->x, point->y, point->len); ecc_hal_set_mode(ECC_MODE_VERIFY); From 4751d66bd060549349e5bac695a154a158a59a65 Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Mon, 6 Jul 2026 17:28:11 +0800 Subject: [PATCH 09/11] fix(esp_tee): guard calloc overflow and attestation leak --- .../components/attestation/esp_att_utils_crypto.c | 14 +++++++++++++- .../esp_tee/subproject/main/common/multi_heap.c | 10 ++++++++-- components/mbedtls/port/aes/esp_aes_gcm.c | 4 ++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/components/esp_tee/subproject/components/attestation/esp_att_utils_crypto.c b/components/esp_tee/subproject/components/attestation/esp_att_utils_crypto.c index 1ea6b724968..ad0a9a9c589 100644 --- a/components/esp_tee/subproject/components/attestation/esp_att_utils_crypto.c +++ b/components/esp_tee/subproject/components/attestation/esp_att_utils_crypto.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -302,6 +302,11 @@ esp_err_t esp_att_utils_ecdsa_get_sign(const esp_att_ecdsa_keypair_t *keypair, c return ESP_ERR_INVALID_SIZE; } + /* Initialise the out-params up front so the error path at 'exit' can free them safely even + * when we bail out (e.g. signature generation fails) before they are allocated. */ + *sign_r_hexstr = NULL; + *sign_s_hexstr = NULL; + esp_err_t err = ESP_FAIL; unsigned char sign_r[SECP256R1_ECDSA_KEY_LEN] = {0}, sign_s[SECP256R1_ECDSA_KEY_LEN] = {0}; @@ -340,5 +345,12 @@ esp_err_t esp_att_utils_ecdsa_get_sign(const esp_att_ecdsa_keypair_t *keypair, c err = ESP_OK; exit: + if (err != ESP_OK) { + /* free(NULL) is a no-op, so this is safe whether or not the buffers were allocated. */ + free(*sign_r_hexstr); + *sign_r_hexstr = NULL; + free(*sign_s_hexstr); + *sign_s_hexstr = NULL; + } return err; } diff --git a/components/esp_tee/subproject/main/common/multi_heap.c b/components/esp_tee/subproject/main/common/multi_heap.c index cceb600052d..ade9f35f759 100644 --- a/components/esp_tee/subproject/main/common/multi_heap.c +++ b/components/esp_tee/subproject/main/common/multi_heap.c @@ -107,7 +107,10 @@ void *esp_tee_heap_malloc(size_t size) void *esp_tee_heap_calloc(size_t n, size_t size) { - size_t reg_size = n * size; + size_t reg_size; + if (__builtin_mul_overflow(n, size, ®_size)) { + return NULL; + } void *ptr = esp_tee_heap_malloc(reg_size); if (ptr != NULL) { memset(ptr, 0x00, reg_size); @@ -241,7 +244,10 @@ void *heap_caps_aligned_alloc(size_t alignment, size_t size, uint32_t caps) void *heap_caps_aligned_calloc(size_t alignment, size_t n, size_t size, uint32_t caps) { (void) caps; - uint32_t reg_size = n * size; + size_t reg_size; + if (__builtin_mul_overflow(n, size, ®_size)) { + return NULL; + } void *ptr = esp_tee_heap_aligned_alloc(reg_size, alignment); if (ptr != NULL) { diff --git a/components/mbedtls/port/aes/esp_aes_gcm.c b/components/mbedtls/port/aes/esp_aes_gcm.c index 9e5ac70aa04..636ba5f9746 100644 --- a/components/mbedtls/port/aes/esp_aes_gcm.c +++ b/components/mbedtls/port/aes/esp_aes_gcm.c @@ -582,6 +582,10 @@ int esp_aes_gcm_finish( esp_gcm_context *ctx, uint8_t len_block[AES_BLOCK_BYTES] = {0}; uint8_t stream[AES_BLOCK_BYTES] = {0}; + (void)output; + (void)output_size; + *output_length = 0; + if ( tag_len > 16 || tag_len < 4 ) { return ( MBEDTLS_ERR_GCM_BAD_INPUT ); } From 25f5e205cdded2e52b9866ff88b533a03aa01354 Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Mon, 6 Jul 2026 17:44:14 +0800 Subject: [PATCH 10/11] fix(mbedtls): bound *iv_off in DMA esp_aes_crypt_ofb to prevent OOB read --- components/mbedtls/port/aes/dma/esp_aes.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/components/mbedtls/port/aes/dma/esp_aes.c b/components/mbedtls/port/aes/dma/esp_aes.c index fcd4ed8a203..0bd75f6bc15 100644 --- a/components/mbedtls/port/aes/dma/esp_aes.c +++ b/components/mbedtls/port/aes/dma/esp_aes.c @@ -436,6 +436,15 @@ int esp_aes_crypt_ofb(esp_aes_context *ctx, n = *iv_off; + /* iv[] is a fixed AES_BLOCK_BYTES buffer and n indexes it directly (before the modulo update), + * so a caller-supplied *iv_off >= AES_BLOCK_BYTES -- attacker-controlled via the TEE secure + * service -- is an out-of-bounds read of iv[] in TEE context (CWE-125), leaking adjacent + * memory into the output. Bound it here, matching the block esp_aes_crypt_ofb() variant. */ + if (n >= AES_BLOCK_BYTES) { + ESP_LOGE(TAG, "IV offset out of bounds"); + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + /* If there is an offset then use the output of the previous AES block (the updated IV) to calculate the new output */ while (n > 0 && length > 0) { From 2312716f8c2a16f8a2d26db475db16cde18560b4 Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Tue, 25 Aug 2026 13:58:16 +0800 Subject: [PATCH 11/11] fix(esp_http_client): return an error when append_string realloc fails --- components/esp_http_client/lib/http_utils.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp_http_client/lib/http_utils.c b/components/esp_http_client/lib/http_utils.c index e78f09edc36..c2b6409629e 100644 --- a/components/esp_http_client/lib/http_utils.c +++ b/components/esp_http_client/lib/http_utils.c @@ -69,7 +69,7 @@ char *http_utils_append_string(char **str, const char *new_str, int len) if (old_str) { old_len = strlen(old_str); old_str = realloc(old_str, old_len + l + 1); - mem_check(old_str); + ESP_RETURN_ON_FALSE(old_str, NULL, TAG, "Memory exhausted"); // Ensure the new string is null-terminated old_str[old_len + l] = 0; } else {