fix(jpeg): validate encoder buffer sizes before header/DMA access

Bound JPEG header emission by outbuf capacity and reject undersized
input buffers before programming TX DMA to avoid heap corruption and
out-of-bounds DMA reads.
This commit is contained in:
C.S.M
2026-08-25 16:29:16 +08:00
parent 5de7d85e4e
commit a7ac00b812
4 changed files with 102 additions and 10 deletions
+23 -1
View File
@@ -6,6 +6,7 @@
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
@@ -42,6 +43,7 @@ static void s_jpeg_enc_config_picture_color_space(jpeg_encoder_handle_t encoder_
static void s_jpeg_enc_select_sample_mode(jpeg_encoder_handle_t encoder_engine);
static void s_encoder_error_log_print(uint32_t status);
static esp_err_t jpeg_enc_validate_sub_sample(jpeg_enc_src_type_t color_space, jpeg_down_sampling_type_t sub_sample);
static esp_err_t jpeg_enc_check_inbuf_size(uint32_t width, uint32_t height, uint32_t bit_depth, uint32_t inbuf_size);
static void jpeg_encoder_isr_handle_default(void *arg)
{
@@ -65,6 +67,7 @@ static void jpeg_encoder_isr_handle_default(void *arg)
static esp_err_t s_jpeg_set_header_info(jpeg_encoder_handle_t encoder_engine)
{
encoder_engine->header_info->header_len = 0;
encoder_engine->header_info->header_buf_overflow = false;
ESP_RETURN_ON_ERROR(emit_soi_marker(encoder_engine->header_info), TAG, "marker emit failed");
ESP_RETURN_ON_ERROR(emit_app0_marker(encoder_engine->header_info), TAG, "marker emit failed");
ESP_RETURN_ON_ERROR(emit_dqt_marker(encoder_engine->header_info), TAG, "marker emit failed");
@@ -100,6 +103,19 @@ static esp_err_t jpeg_enc_validate_sub_sample(jpeg_enc_src_type_t color_space, j
return ESP_OK;
}
static esp_err_t jpeg_enc_check_inbuf_size(uint32_t width, uint32_t height, uint32_t bit_depth, uint32_t inbuf_size)
{
ESP_RETURN_ON_FALSE(width > 0 && height > 0 && bit_depth > 0, ESP_ERR_INVALID_ARG, TAG, "invalid image geometry");
uint64_t required_bits = (uint64_t)width * height * bit_depth;
ESP_RETURN_ON_FALSE((required_bits % 8) == 0, ESP_ERR_INVALID_ARG, TAG, "image size is not byte aligned");
uint64_t required_size = required_bits / 8;
ESP_RETURN_ON_FALSE(required_size <= inbuf_size, ESP_ERR_INVALID_ARG, TAG,
"input buffer is too small for width=%" PRIu32 " height=%" PRIu32 " bit_depth=%" PRIu32
" (need %" PRIu64 ", got %" PRIu32 ")",
width, height, bit_depth, required_size, inbuf_size);
return ESP_OK;
}
esp_err_t jpeg_new_encoder_engine(const jpeg_encode_engine_cfg_t *enc_eng_cfg, jpeg_encoder_handle_t *ret_encoder)
{
#if CONFIG_JPEG_ENABLE_DEBUG_LOG
@@ -132,7 +148,7 @@ esp_err_t jpeg_new_encoder_engine(const jpeg_encode_engine_cfg_t *enc_eng_cfg, j
ESP_GOTO_ON_ERROR(jpeg_check_intr_priority(encoder_engine->codec_base, enc_eng_cfg->intr_priority), err, TAG, "set group interrupt priority failed");
if (enc_eng_cfg->intr_priority) {
ESP_RETURN_ON_FALSE(1 << (enc_eng_cfg->intr_priority) & JPEG_ALLOW_INTR_PRIORITY_MASK, ESP_ERR_INVALID_ARG, TAG, "invalid interrupt priority:%d", enc_eng_cfg->intr_priority);
ESP_GOTO_ON_FALSE(1 << (enc_eng_cfg->intr_priority) & JPEG_ALLOW_INTR_PRIORITY_MASK, ESP_ERR_INVALID_ARG, err, TAG, "invalid interrupt priority:%d", enc_eng_cfg->intr_priority);
}
int isr_flags = JPEG_INTR_ALLOC_FLAG;
if (enc_eng_cfg->intr_priority) {
@@ -243,9 +259,14 @@ esp_err_t jpeg_encoder_process(jpeg_encoder_handle_t encoder_engine, const jpeg_
encoder_engine->header_info->origin_h = encode_cfg->width;
encoder_engine->header_info->origin_v = encode_cfg->height;
encoder_engine->header_info->header_buf = bit_stream;
encoder_engine->header_info->header_buf_size = outbuf_size;
s_jpeg_enc_config_picture_color_space(encoder_engine);
s_jpeg_enc_select_sample_mode(encoder_engine);
/* bytes_per_pixel holds bit depth; reject before programming TX DMA. */
ESP_GOTO_ON_ERROR(jpeg_enc_check_inbuf_size(encode_cfg->width, encode_cfg->height,
encoder_engine->bytes_per_pixel, inbuf_size),
err2, TAG, "input buffer size check failed");
jpeg_ll_set_picture_height(hal->dev, encoder_engine->header_info->origin_v);
jpeg_ll_set_picture_width(hal->dev, encoder_engine->header_info->origin_h);
jpeg_ll_pixel_reverse(hal->dev, encode_cfg->pixel_reverse);
@@ -254,6 +275,7 @@ esp_err_t jpeg_encoder_process(jpeg_encoder_handle_t encoder_engine, const jpeg_
jpeg_ll_set_qnr_presition(hal->dev, 0);
ESP_GOTO_ON_ERROR(s_jpeg_set_header_info(encoder_engine), err2, TAG, "set header failed");
jpeg_hal_set_quantization_coefficient(hal, encoder_engine->header_info->m_quantization_tables[0], encoder_engine->header_info->m_quantization_tables[1]);
/* Need at least one byte of payload room after a complete header. */
ESP_GOTO_ON_FALSE(outbuf_size > encoder_engine->header_info->header_len, ESP_ERR_INVALID_ARG, err2, TAG, "output buffer is too small for jpeg header");
payload_buf_size = outbuf_size - encoder_engine->header_info->header_len;