From 403074177fa4f59a8916e382fccf9eeba55338d7 Mon Sep 17 00:00:00 2001 From: morris Date: Tue, 30 Jun 2026 16:52:13 +0800 Subject: [PATCH 1/6] fix(adc): add missing input validation for channel and ret_handle - adc_cali_curve_fitting: validate config->chan in check_valid() to prevent OOB access into s_adc_cali_chan_compens compensation table - adc_filter: make s_adc_filter_free idempotent on !UNIT_BINDED SoCs to prevent double-free on repeated adc_del_continuous_iir_filter - adc_cali_line_fitting(esp32): fix config && config typo to config && ret_handle, preventing NULL-pointer dereference --- components/esp_adc/adc_cali_curve_fitting.c | 1 + components/esp_adc/adc_filter.c | 8 ++++++-- components/esp_adc/esp32/adc_cali_line_fitting.c | 5 ++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/components/esp_adc/adc_cali_curve_fitting.c b/components/esp_adc/adc_cali_curve_fitting.c index c59ee2b0c74..4c2af48fbe2 100644 --- a/components/esp_adc/adc_cali_curve_fitting.c +++ b/components/esp_adc/adc_cali_curve_fitting.c @@ -221,6 +221,7 @@ static esp_err_t check_valid(const adc_cali_curve_fitting_config_t *config) { ESP_RETURN_ON_FALSE(config->unit_id < SOC_ADC_PERIPH_NUM, ESP_ERR_INVALID_ARG, TAG, "invalid ADC unit"); ESP_RETURN_ON_FALSE(config->atten < SOC_ADC_ATTEN_NUM, ESP_ERR_INVALID_ARG, TAG, "invalid ADC attenuation"); + ESP_RETURN_ON_FALSE(config->chan < SOC_ADC_CHANNEL_NUM(config->unit_id), ESP_ERR_INVALID_ARG, TAG, "invalid ADC channel"); bool available_oneshot_bitwidth = (config->bitwidth >= SOC_ADC_RTC_MIN_BITWIDTH && config->bitwidth <= SOC_ADC_RTC_MAX_BITWIDTH); bool available_dma_bitwidth = (config->bitwidth >= SOC_ADC_DIGI_MIN_BITWIDTH && config->bitwidth <= SOC_ADC_DIGI_MAX_BITWIDTH); diff --git a/components/esp_adc/adc_filter.c b/components/esp_adc/adc_filter.c index da8ead99ea5..2f6c2a6ac8c 100644 --- a/components/esp_adc/adc_filter.c +++ b/components/esp_adc/adc_filter.c @@ -78,11 +78,15 @@ static esp_err_t s_adc_filter_claim(adc_continuous_handle_t handle, adc_iir_filt static esp_err_t s_adc_filter_free(adc_iir_filter_t *filter_ctx) { assert(filter_ctx); + esp_err_t ret = ESP_ERR_NOT_FOUND; portENTER_CRITICAL(&s_filter_spinlock); - filter_ctx->continuous_ctx->iir_filter[filter_ctx->filter_id] = NULL; + if (filter_ctx->continuous_ctx->iir_filter[filter_ctx->filter_id] != NULL) { + filter_ctx->continuous_ctx->iir_filter[filter_ctx->filter_id] = NULL; + ret = ESP_OK; + } portEXIT_CRITICAL(&s_filter_spinlock); - return ESP_OK; + return ret; } #endif diff --git a/components/esp_adc/esp32/adc_cali_line_fitting.c b/components/esp_adc/esp32/adc_cali_line_fitting.c index 60710818584..57175d56106 100644 --- a/components/esp_adc/esp32/adc_cali_line_fitting.c +++ b/components/esp_adc/esp32/adc_cali_line_fitting.c @@ -154,7 +154,7 @@ typedef struct { esp_err_t adc_cali_create_scheme_line_fitting(const adc_cali_line_fitting_config_t *config, adc_cali_handle_t *ret_handle) { esp_err_t ret = ESP_OK; - ESP_RETURN_ON_FALSE(config && config, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer"); + ESP_RETURN_ON_FALSE(config && ret_handle, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer"); ESP_RETURN_ON_FALSE(config->unit_id < SOC_ADC_PERIPH_NUM, ESP_ERR_INVALID_ARG, TAG, "invalid ADC unit"); ESP_RETURN_ON_FALSE(config->atten < SOC_ADC_ATTEN_NUM, ESP_ERR_INVALID_ARG, TAG, "invalid ADC attenuation"); ESP_RETURN_ON_FALSE(((config->bitwidth >= SOC_ADC_RTC_MIN_BITWIDTH && config->bitwidth <= SOC_ADC_RTC_MAX_BITWIDTH) || config->bitwidth == ADC_BITWIDTH_DEFAULT), ESP_ERR_INVALID_ARG, TAG, "invalid bitwidth"); @@ -204,6 +204,9 @@ esp_err_t adc_cali_create_scheme_line_fitting(const adc_cali_line_fitting_config return ESP_OK; err: + if (chars) { + free(chars); + } if (scheme) { free(scheme); } From 2c2f5ebf20c8e600a25ba0eeebb032b8301105c9 Mon Sep 17 00:00:00 2001 From: morris Date: Tue, 30 Jun 2026 16:52:19 +0800 Subject: [PATCH 2/6] fix(csi): move csi_fsm init before resource allocation to fix err-path leak CSI_FSM_INIT is 1, but the controller struct is zero-allocated. Any failure before the former csi_fsm assignment (near the end of esp_cam_new_csi_ctlr) jumped to err: which called s_del_csi_ctlr. That function bailed out immediately because csi_fsm == 0, leaking the claimed slot, queue, bridge, DMA channel, PM lock, and backup buffer. Move csi_fsm = CSI_FSM_INIT right after a successful claim so the err: path properly tears down all allocated resources. --- components/esp_driver_cam/csi/src/esp_cam_ctlr_csi.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/components/esp_driver_cam/csi/src/esp_cam_ctlr_csi.c b/components/esp_driver_cam/csi/src/esp_cam_ctlr_csi.c index fea9748a9b1..cda591757bb 100644 --- a/components/esp_driver_cam/csi/src/esp_cam_ctlr_csi.c +++ b/components/esp_driver_cam/csi/src/esp_cam_ctlr_csi.c @@ -126,6 +126,7 @@ esp_err_t esp_cam_new_csi_ctlr(const esp_cam_ctlr_csi_config_t *config, esp_cam_ free(ctlr); ESP_RETURN_ON_ERROR(ret, TAG, "no available csi controller"); } + ctlr->csi_fsm = CSI_FSM_INIT; ESP_LOGD(TAG, "config->queue_items: %d", config->queue_items); ctlr->trans_que = xQueueCreateWithCaps(config->queue_items, sizeof(esp_cam_ctlr_trans_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); @@ -262,7 +263,6 @@ esp_err_t esp_cam_new_csi_ctlr(const esp_cam_ctlr_csi_config_t *config, esp_cam_ #endif //CONFIG_PM_ENABLE ctlr->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED; - ctlr->csi_fsm = CSI_FSM_INIT; ctlr->base.del = s_ctlr_del; ctlr->base.enable = s_csi_ctlr_enable; ctlr->base.start = s_ctlr_csi_start; @@ -313,7 +313,9 @@ esp_err_t s_del_csi_ctlr(csi_controller_t *ctlr) if (!ctlr->bk_buffer_dis) { free(ctlr->backup_buffer); } - vQueueDeleteWithCaps(ctlr->trans_que); + if (ctlr->trans_que) { + vQueueDeleteWithCaps(ctlr->trans_que); + } free(ctlr); return ESP_OK; From cd2838308837c1bb9a97d4fd81e7920400c16216 Mon Sep 17 00:00:00 2001 From: morris Date: Tue, 30 Jun 2026 16:52:21 +0800 Subject: [PATCH 3/6] fix(i2c): release platform mutex on intr/pm_lock delete failure ESP_RETURN_ON_ERROR inside the s_i2c_platform.mutex critical section returns without releasing the mutex, permanently blocking all I2C bus operations. Replace with ESP_GOTO_ON_ERROR that jumps to a cleanup label releasing the mutex before return. --- components/esp_driver_i2c/i2c_common.c | 36 +++++++++++++++----------- components/esp_driver_i2c/i2c_master.c | 3 +++ components/esp_driver_i2c/i2c_slave.c | 5 ++++ 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/components/esp_driver_i2c/i2c_common.c b/components/esp_driver_i2c/i2c_common.c index b80aa0d97ea..79da1df1c2a 100644 --- a/components/esp_driver_i2c/i2c_common.c +++ b/components/esp_driver_i2c/i2c_common.c @@ -190,15 +190,27 @@ esp_err_t i2c_acquire_bus_handle(i2c_port_num_t port_num, i2c_bus_handle_t *i2c_ esp_err_t i2c_release_bus_handle(i2c_bus_handle_t i2c_bus) { + esp_err_t ret = ESP_OK; int port_num = i2c_bus->port_num; i2c_clock_source_t clk_src = i2c_bus->clk_src; bool do_deinitialize = false; _lock_acquire(&s_i2c_platform.mutex); if (s_i2c_platform.buses[port_num]) { - s_i2c_platform.count[port_num]--; - if (s_i2c_platform.count[port_num] == 0) { + if (s_i2c_platform.count[port_num] > 1) { + s_i2c_platform.count[port_num]--; + } else { do_deinitialize = true; - s_i2c_platform.buses[port_num] = NULL; + if (i2c_bus->intr_handle) { + ESP_GOTO_ON_ERROR(esp_intr_free(i2c_bus->intr_handle), err, TAG, "delete interrupt service failed"); + i2c_bus->intr_handle = NULL; + } +#if CONFIG_PM_ENABLE + if (i2c_bus->pm_lock) { + esp_pm_lock_delete(i2c_bus->pm_lock); + i2c_bus->pm_lock = NULL; + } +#endif + esp_clk_tree_enable_src(clk_src, false); #if I2C_USE_RETENTION_LINK if (i2c_bus->is_lp_i2c == false) { if (i2c_bus->retention_link_created) { @@ -207,14 +219,8 @@ esp_err_t i2c_release_bus_handle(i2c_bus_handle_t i2c_bus) sleep_retention_module_deinit(i2c_regs_retention[port_num].module_id); } #endif - if (i2c_bus->intr_handle) { - ESP_RETURN_ON_ERROR(esp_intr_free(i2c_bus->intr_handle), TAG, "delete interrupt service failed"); - } -#if CONFIG_PM_ENABLE - if (i2c_bus->pm_lock) { - ESP_RETURN_ON_ERROR(esp_pm_lock_delete(i2c_bus->pm_lock), TAG, "delete pm_lock failed"); - } -#endif + s_i2c_platform.count[port_num] = 0; + s_i2c_platform.buses[port_num] = NULL; // Disable I2C module if (!i2c_bus->is_lp_i2c) { PERIPH_RCC_ATOMIC() { @@ -233,14 +239,14 @@ esp_err_t i2c_release_bus_handle(i2c_bus_handle_t i2c_bus) } _lock_release(&s_i2c_platform.mutex); - ESP_RETURN_ON_ERROR(esp_clk_tree_enable_src(clk_src, false), TAG, "clock source clock disable failed"); - if (do_deinitialize) { ESP_LOGD(TAG, "delete bus %d", port_num); } - - ESP_RETURN_ON_FALSE(s_i2c_platform.count[port_num] == 0, ESP_ERR_INVALID_STATE, TAG, "Bus not freed entirely"); return ESP_OK; + +err: + _lock_release(&s_i2c_platform.mutex); + return ret; } esp_err_t i2c_select_periph_clock(i2c_bus_handle_t handle, soc_module_clk_t clk_src) diff --git a/components/esp_driver_i2c/i2c_master.c b/components/esp_driver_i2c/i2c_master.c index 811fc457f1c..bb7c13fb171 100644 --- a/components/esp_driver_i2c/i2c_master.c +++ b/components/esp_driver_i2c/i2c_master.c @@ -907,6 +907,9 @@ static esp_err_t i2c_master_bus_destroy(i2c_master_bus_handle_t bus_handle) if (err == ESP_OK) { err = release_ret; } + // Non-OK here means interrupt teardown did not complete, so the ISR + // may still reference i2c_master and its wrapper-owned resources. + return err; } } diff --git a/components/esp_driver_i2c/i2c_slave.c b/components/esp_driver_i2c/i2c_slave.c index a367fb44445..9c6e23589bc 100644 --- a/components/esp_driver_i2c/i2c_slave.c +++ b/components/esp_driver_i2c/i2c_slave.c @@ -219,6 +219,11 @@ static esp_err_t i2c_slave_device_destroy(i2c_slave_dev_handle_t i2c_slave) i2c_ll_disable_intr_mask(i2c_slave->base->hal.dev, I2C_LL_SLAVE_EVENT_INTR); i2c_common_deinit_pins(i2c_slave->base); ret = i2c_release_bus_handle(i2c_slave->base); + if (ret != ESP_OK) { + // Non-OK here means interrupt teardown did not complete, so the ISR + // may still reference i2c_slave and its wrapper-owned resources. + return ret; + } } if (i2c_slave->rx_ring_buf) { vRingbufferDeleteWithCaps(i2c_slave->rx_ring_buf); From 2ab4b39ce52722c906654730e2b44a64e6a1019a Mon Sep 17 00:00:00 2001 From: morris Date: Tue, 30 Jun 2026 16:52:23 +0800 Subject: [PATCH 4/6] fix(jpeg): release platform mutex on semaphore/pm-lock allocation failure jpeg_acquire_codec_handle acquires s_jpeg_platform.mutex at entry but two ESP_RETURN_ON_* macros (semaphore-create and PM-lock-create failure) return without releasing it. Replace with ESP_GOTO_ON_* that jumps to a cleanup label which frees partial resources, NULLs the codec pointer, and releases the mutex. --- components/esp_driver_jpeg/jpeg_common.c | 38 ++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/components/esp_driver_jpeg/jpeg_common.c b/components/esp_driver_jpeg/jpeg_common.c index 5028c864442..7edc931dba9 100644 --- a/components/esp_driver_jpeg/jpeg_common.c +++ b/components/esp_driver_jpeg/jpeg_common.c @@ -71,6 +71,10 @@ esp_err_t jpeg_acquire_codec_handle(jpeg_codec_handle_t *jpeg_new_codec) #endif esp_err_t ret = ESP_OK; bool new_codec = false; +#if JPEG_USE_RETENTION_LINK + bool retention_module_inited = false; +#endif + bool bus_clock_enabled = false; jpeg_codec_t *codec = NULL; _lock_acquire(&s_jpeg_platform.mutex); if (!s_jpeg_platform.jpeg_codec) { @@ -81,7 +85,7 @@ esp_err_t jpeg_acquire_codec_handle(jpeg_codec_handle_t *jpeg_new_codec) codec->intr_priority = -1; codec->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED; codec->codec_mutex = xSemaphoreCreateBinaryWithCaps(JPEG_MEM_ALLOC_CAPS); - ESP_RETURN_ON_FALSE(codec->codec_mutex, ESP_ERR_NO_MEM, TAG, "No memory for codec mutex"); + ESP_GOTO_ON_FALSE(codec->codec_mutex, ESP_ERR_NO_MEM, err, TAG, "No memory for codec mutex"); SLIST_INIT(&codec->jpeg_isr_handler_list); xSemaphoreGive(codec->codec_mutex); @@ -99,6 +103,8 @@ esp_err_t jpeg_acquire_codec_handle(jpeg_codec_handle_t *jpeg_new_codec) esp_err_t err = sleep_retention_module_init(jpeg_regs_retention.module_id, &init_param); if (err != ESP_OK) { ESP_LOGW(TAG, "init sleep retention failed on jpeg, jpeg configuration maybe lost after sleep wakeup"); + } else { + retention_module_inited = true; } #endif // init the clock @@ -106,8 +112,10 @@ esp_err_t jpeg_acquire_codec_handle(jpeg_codec_handle_t *jpeg_new_codec) jpeg_ll_enable_bus_clock(true); jpeg_ll_reset_module_register(); } + bus_clock_enabled = true; #if CONFIG_PM_ENABLE - ESP_RETURN_ON_ERROR(esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "jpeg_codec", &codec->pm_lock), TAG, "create pm lock failed"); + ESP_GOTO_ON_ERROR(esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "jpeg_codec", &codec->pm_lock), + err, TAG, "create pm lock failed"); #endif jpeg_hal_init(&codec->hal); } else { @@ -126,6 +134,32 @@ esp_err_t jpeg_acquire_codec_handle(jpeg_codec_handle_t *jpeg_new_codec) *jpeg_new_codec = s_jpeg_platform.jpeg_codec; _lock_release(&s_jpeg_platform.mutex); return ret; + +err: + if (codec) { + if (codec->codec_mutex) { + vSemaphoreDeleteWithCaps(codec->codec_mutex); + } +#if CONFIG_PM_ENABLE + if (codec->pm_lock) { + esp_pm_lock_delete(codec->pm_lock); + } +#endif +#if JPEG_USE_RETENTION_LINK + if (retention_module_inited) { + sleep_retention_module_deinit(jpeg_regs_retention.module_id); + } +#endif + if (bus_clock_enabled) { + PERIPH_RCC_ATOMIC() { + jpeg_ll_enable_bus_clock(false); + } + } + free(codec); + } + s_jpeg_platform.jpeg_codec = NULL; + _lock_release(&s_jpeg_platform.mutex); + return ret; } esp_err_t jpeg_release_codec_handle(jpeg_codec_handle_t jpeg_codec) From f1cc319c2d2f6d7773f5c16ac4ba0eaaf8cd1f45 Mon Sep 17 00:00:00 2001 From: morris Date: Tue, 30 Jun 2026 16:52:26 +0800 Subject: [PATCH 5/6] fix(spi_slave): free DMA-private buffers when transaction queue is full spi_slave_queue_trans calls spi_slave_setup_priv_trans to allocate DMA buffers, then tries xQueueSend. If the queue is full the function returns ESP_ERR_TIMEOUT without freeing those buffers, leaking up to 2 * max_transfer_sz per failed call. Call spi_slave_uninstall_priv_trans before returning the timeout. --- components/esp_driver_spi/src/gpspi/spi_slave.c | 1 + 1 file changed, 1 insertion(+) diff --git a/components/esp_driver_spi/src/gpspi/spi_slave.c b/components/esp_driver_spi/src/gpspi/spi_slave.c index 667d2b4995d..3d35b35624f 100644 --- a/components/esp_driver_spi/src/gpspi/spi_slave.c +++ b/components/esp_driver_spi/src/gpspi/spi_slave.c @@ -453,6 +453,7 @@ esp_err_t SPI_SLAVE_ATTR spi_slave_queue_trans(spi_host_device_t host, const spi r = xQueueSend(spihost[host]->trans_queue, (void *)&priv_trans, ticks_to_wait); if (!r) { + spi_slave_uninstall_priv_trans(host, &priv_trans); return ESP_ERR_TIMEOUT; } esp_intr_enable(spihost[host]->intr); From 0f3a788f167ed5286a753d7ae98b9a92459d30d4 Mon Sep 17 00:00:00 2001 From: morris Date: Tue, 30 Jun 2026 17:24:54 +0800 Subject: [PATCH 6/6] fix(sdspi): reject oversized pre-read data before block receive Guard start_command_read_blocks against cards that place TOKEN_BLOCK_START so early that extra_data_size exceeds the bytes expected on the current iteration. Without this check, the unsigned subtraction for will_receive underflows and propagates into memset, SPI transaction length, and memcpy counts against the fixed 516-byte block buffer. --- components/esp_driver_sdspi/src/sdspi_host.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/components/esp_driver_sdspi/src/sdspi_host.c b/components/esp_driver_sdspi/src/sdspi_host.c index 99c2a0b5b5e..9ef4955bd71 100644 --- a/components/esp_driver_sdspi/src/sdspi_host.c +++ b/components/esp_driver_sdspi/src/sdspi_host.c @@ -810,7 +810,12 @@ static esp_err_t start_command_read_blocks(slot_info_t *slot, sdspi_hw_cmd_t *cm } // Arrange RX buffer - size_t will_receive = MIN(rx_length, SDSPI_MAX_DATA_LEN) - extra_data_size; + size_t expected_data_size = MIN(rx_length, SDSPI_MAX_DATA_LEN); + if (extra_data_size > expected_data_size) { + ESP_LOGD(TAG, "%s: invalid extra data size %u (expected <= %u)", __func__, (unsigned)extra_data_size, (unsigned)expected_data_size); + return ESP_ERR_INVALID_RESPONSE; + } + size_t will_receive = expected_data_size - extra_data_size; uint8_t* rx_data; ret = get_block_buf(slot, &rx_data); if (ret != ESP_OK) {