Commit 023743a7d7 for aom
commit 023743a7d78d9c7d33390c5c45b35495ea013a21
Author: Lin Zheng <linzhen@google.com>
Date: Wed Sep 9 18:06:00 2026 +0000
Fix memcpy-param-overlap in disflow flow-field border filling
In fill_flow_field_borders() and upscale_flow_component(), ensure that
the copy length does not exceed stride before copying row borders, and
use memmove() instead of memcpy() to prevent undefined behavior if
source and destination ranges overlap.
It also fixed heap-buffer-overflow on dynamic resolution change - update
cpi->frame_info in av1_update_frame_size() and track allocation sizes
for mb_weber_stats and mb_delta_q, reallocating them when dimensions increase.
Bug: 558524775
Change-Id: Id0df83fbcf4b9ec7f9efe441875c8d54e6eb0efe
diff --git a/aom_dsp/flow_estimation/corner_match.c b/aom_dsp/flow_estimation/corner_match.c
index 419884b8e6..7a00e1fdfc 100644
--- a/aom_dsp/flow_estimation/corner_match.c
+++ b/aom_dsp/flow_estimation/corner_match.c
@@ -290,7 +290,8 @@ bool av1_compute_global_motion_feature_match(
assert(ref_pyramid->layers[0].height == src_height);
const int ref_stride = ref_pyramid->layers[0].stride;
- if (ref_stride != src_stride) {
+ if (ref_stride != src_stride || src_corners->num_corners == 0 ||
+ ref_corners->num_corners == 0) {
return false;
}
diff --git a/aom_dsp/flow_estimation/disflow.c b/aom_dsp/flow_estimation/disflow.c
index cc1b56c843..6a41642d52 100644
--- a/aom_dsp/flow_estimation/disflow.c
+++ b/aom_dsp/flow_estimation/disflow.c
@@ -447,6 +447,9 @@ void aom_compute_flow_at_point_c(const uint8_t *src, const uint8_t *ref, int x,
static void fill_flow_field_borders(double *flow, int width, int height,
int stride) {
+ if (width <= 2 * FLOW_BORDER_INNER || height <= 2 * FLOW_BORDER_INNER) return;
+ assert(width + 2 * FLOW_BORDER_OUTER <= stride);
+
// Calculate the bounds of the rectangle which was filled in by
// compute_flow_field() before calling this function.
// These indices are inclusive on both ends.
@@ -478,7 +481,7 @@ static void fill_flow_field_borders(double *flow, int width, int height,
for (int i = -FLOW_BORDER_OUTER; i < top_index; i++) {
double *row = flow + i * stride - FLOW_BORDER_OUTER;
size_t length = width + 2 * FLOW_BORDER_OUTER;
- memcpy(row, top_row, length * sizeof(*row));
+ memmove(row, top_row, length * sizeof(*row));
}
// Bottom area
@@ -486,7 +489,7 @@ static void fill_flow_field_borders(double *flow, int width, int height,
for (int i = bottom_index + 1; i < height + FLOW_BORDER_OUTER; i++) {
double *row = flow + i * stride - FLOW_BORDER_OUTER;
size_t length = width + 2 * FLOW_BORDER_OUTER;
- memcpy(row, bottom_row, length * sizeof(*row));
+ memmove(row, bottom_row, length * sizeof(*row));
}
}
@@ -541,6 +544,10 @@ static void fill_flow_field_borders(double *flow, int width, int height,
// vector, even though these must be interpolated using different source points.
static void upscale_flow_component(double *flow, int cur_width, int cur_height,
int stride, double *tmpbuf) {
+ assert(cur_width > 0);
+ assert(cur_height > 0);
+ assert(2 * cur_width <= stride);
+
const int half_len = FLOW_UPSCALE_TAPS / 2;
// Check that the outer border is large enough to avoid needing to clamp
@@ -571,13 +578,13 @@ static void upscale_flow_component(double *flow, int cur_width, int cur_height,
const double *top_row = &tmpbuf[0];
for (int i = -FLOW_BORDER_OUTER; i < 0; i++) {
double *row = &tmpbuf[i * stride];
- memcpy(row, top_row, 2 * cur_width * sizeof(*row));
+ memmove(row, top_row, 2 * cur_width * sizeof(*row));
}
const double *bottom_row = &tmpbuf[(cur_height - 1) * stride];
for (int i = cur_height; i < cur_height + FLOW_BORDER_OUTER; i++) {
double *row = &tmpbuf[i * stride];
- memcpy(row, bottom_row, 2 * cur_width * sizeof(*row));
+ memmove(row, bottom_row, 2 * cur_width * sizeof(*row));
}
// Vertical upscale
@@ -728,12 +735,17 @@ free_tmpbuf:
}
static FlowField *alloc_flow_field(int frame_width, int frame_height) {
+ const int flow_width = frame_width >> DOWNSAMPLE_SHIFT;
+ const int flow_height = frame_height >> DOWNSAMPLE_SHIFT;
+ assert(flow_width > 0);
+ assert(flow_height > 0);
+
FlowField *flow = (FlowField *)aom_malloc(sizeof(FlowField));
if (flow == NULL) return NULL;
// Calculate the size of the bottom (largest) layer of the flow pyramid
- flow->width = frame_width >> DOWNSAMPLE_SHIFT;
- flow->height = frame_height >> DOWNSAMPLE_SHIFT;
+ flow->width = flow_width;
+ flow->height = flow_height;
flow->stride = flow->width + 2 * FLOW_BORDER_OUTER;
const size_t flow_size =
@@ -792,8 +804,17 @@ bool av1_compute_global_motion_disflow(
const int src_width = src_pyramid->layers[0].width;
const int src_height = src_pyramid->layers[0].height;
- assert(ref_pyramid->layers[0].width == src_width);
- assert(ref_pyramid->layers[0].height == src_height);
+ if (ref_pyramid->layers[0].width != src_width ||
+ ref_pyramid->layers[0].height != src_height) {
+ return false;
+ }
+ if (src_width < (1 << DOWNSAMPLE_SHIFT) ||
+ src_height < (1 << DOWNSAMPLE_SHIFT)) {
+ return false;
+ }
+ if (src_corners->num_corners == 0) {
+ return false;
+ }
if (ref_pyramid->layers[0].stride != src_pyramid->layers[0].stride) {
return false;
diff --git a/av1/encoder/allintra_vis.c b/av1/encoder/allintra_vis.c
index ec8ec2680d..be21bc9765 100644
--- a/av1/encoder/allintra_vis.c
+++ b/av1/encoder/allintra_vis.c
@@ -59,34 +59,41 @@ void av1_dealloc_mb_wiener_var_pred_buf(ThreadData *td) {
void av1_init_mb_wiener_var_buffer(AV1_COMP *cpi) {
AV1_COMMON *cm = &cpi->common;
+ const int current_size = cpi->frame_info.mi_rows * cpi->frame_info.mi_cols;
// This block size is also used to determine number of workers in
// multi-threading. If it is changed, one needs to change it accordingly in
// "compute_num_ai_workers()".
cpi->weber_bsize = BLOCK_8X8;
- if (cpi->oxcf.enable_rate_guide_deltaq) {
- if (cpi->mb_weber_stats && cpi->prep_rate_estimates &&
- cpi->ext_rate_distribution)
- return;
- } else {
- if (cpi->mb_weber_stats) return;
+ if (cpi->mb_weber_stats && cpi->mb_weber_stats_alloc_size < current_size) {
+ aom_free(cpi->mb_weber_stats);
+ cpi->mb_weber_stats = NULL;
+ aom_free(cpi->prep_rate_estimates);
+ cpi->prep_rate_estimates = NULL;
+ aom_free(cpi->ext_rate_distribution);
+ cpi->ext_rate_distribution = NULL;
+ cpi->mb_weber_stats_alloc_size = 0;
}
- CHECK_MEM_ERROR(cm, cpi->mb_weber_stats,
- aom_calloc(cpi->frame_info.mi_rows * cpi->frame_info.mi_cols,
- sizeof(*cpi->mb_weber_stats)));
+ if (!cpi->mb_weber_stats) {
+ CHECK_MEM_ERROR(cm, cpi->mb_weber_stats,
+ aom_calloc(current_size, sizeof(*cpi->mb_weber_stats)));
+ cpi->mb_weber_stats_alloc_size = current_size;
+ }
if (cpi->oxcf.enable_rate_guide_deltaq) {
- CHECK_MEM_ERROR(
- cm, cpi->prep_rate_estimates,
- aom_calloc(cpi->frame_info.mi_rows * cpi->frame_info.mi_cols,
- sizeof(*cpi->prep_rate_estimates)));
-
- CHECK_MEM_ERROR(
- cm, cpi->ext_rate_distribution,
- aom_calloc(cpi->frame_info.mi_rows * cpi->frame_info.mi_cols,
- sizeof(*cpi->ext_rate_distribution)));
+ if (!cpi->prep_rate_estimates) {
+ CHECK_MEM_ERROR(cm, cpi->prep_rate_estimates,
+ aom_calloc(cpi->mb_weber_stats_alloc_size,
+ sizeof(*cpi->prep_rate_estimates)));
+ }
+
+ if (!cpi->ext_rate_distribution) {
+ CHECK_MEM_ERROR(cm, cpi->ext_rate_distribution,
+ aom_calloc(cpi->mb_weber_stats_alloc_size,
+ sizeof(*cpi->ext_rate_distribution)));
+ }
}
}
@@ -774,12 +781,19 @@ int av1_get_sbq_perceptual_ai(const AV1_COMP *const cpi, BLOCK_SIZE bsize,
void av1_init_mb_ur_var_buffer(AV1_COMP *cpi) {
AV1_COMMON *cm = &cpi->common;
+ const int current_size = cpi->frame_info.mb_rows * cpi->frame_info.mb_cols;
- if (cpi->mb_delta_q) return;
+ if (cpi->mb_delta_q && cpi->mb_delta_q_alloc_size < current_size) {
+ aom_free(cpi->mb_delta_q);
+ cpi->mb_delta_q = NULL;
+ cpi->mb_delta_q_alloc_size = 0;
+ }
- CHECK_MEM_ERROR(cm, cpi->mb_delta_q,
- aom_calloc(cpi->frame_info.mb_rows * cpi->frame_info.mb_cols,
- sizeof(*cpi->mb_delta_q)));
+ if (!cpi->mb_delta_q) {
+ CHECK_MEM_ERROR(cm, cpi->mb_delta_q,
+ aom_calloc(current_size, sizeof(*cpi->mb_delta_q)));
+ cpi->mb_delta_q_alloc_size = current_size;
+ }
}
#if CONFIG_TFLITE
diff --git a/av1/encoder/encoder.c b/av1/encoder/encoder.c
index ccde441ea0..0a8750bcdd 100644
--- a/av1/encoder/encoder.c
+++ b/av1/encoder/encoder.c
@@ -429,6 +429,22 @@ static void set_tile_info(AV1_COMMON *const cm,
av1_calculate_tile_rows(seq_params, mi_params->mi_rows, tiles);
}
+static inline void init_frame_info(FRAME_INFO *frame_info,
+ const AV1_COMMON *const cm) {
+ const CommonModeInfoParams *const mi_params = &cm->mi_params;
+ const SequenceHeader *const seq_params = cm->seq_params;
+ frame_info->frame_width = cm->width;
+ frame_info->frame_height = cm->height;
+ frame_info->mi_cols = mi_params->mi_cols;
+ frame_info->mi_rows = mi_params->mi_rows;
+ frame_info->mb_cols = mi_params->mb_cols;
+ frame_info->mb_rows = mi_params->mb_rows;
+ frame_info->num_mbs = mi_params->MBs;
+ frame_info->bit_depth = seq_params->bit_depth;
+ frame_info->subsampling_x = seq_params->subsampling_x;
+ frame_info->subsampling_y = seq_params->subsampling_y;
+}
+
void av1_update_frame_size(AV1_COMP *cpi) {
AV1_COMMON *const cm = &cpi->common;
MACROBLOCKD *const xd = &cpi->td.mb.e_mbd;
@@ -446,6 +462,7 @@ void av1_update_frame_size(AV1_COMP *cpi) {
cpi->ppi->number_spatial_layers));
set_tile_info(cm, &cpi->oxcf.tile_cfg);
+ init_frame_info(&cpi->frame_info, cm);
}
static inline int does_level_match(int width, int height, double fps,
@@ -1097,22 +1114,6 @@ void av1_change_config(struct AV1_COMP *cpi, const AV1EncoderConfig *oxcf,
#endif // CONFIG_REALTIME_ONLY
}
-static inline void init_frame_info(FRAME_INFO *frame_info,
- const AV1_COMMON *const cm) {
- const CommonModeInfoParams *const mi_params = &cm->mi_params;
- const SequenceHeader *const seq_params = cm->seq_params;
- frame_info->frame_width = cm->width;
- frame_info->frame_height = cm->height;
- frame_info->mi_cols = mi_params->mi_cols;
- frame_info->mi_rows = mi_params->mi_rows;
- frame_info->mb_cols = mi_params->mb_cols;
- frame_info->mb_rows = mi_params->mb_rows;
- frame_info->num_mbs = mi_params->MBs;
- frame_info->bit_depth = seq_params->bit_depth;
- frame_info->subsampling_x = seq_params->subsampling_x;
- frame_info->subsampling_y = seq_params->subsampling_y;
-}
-
static inline void init_frame_index_set(FRAME_INDEX_SET *frame_index_set) {
frame_index_set->show_frame_count = 0;
}
@@ -1620,7 +1621,9 @@ AV1_COMP *av1_create_compressor(AV1_PRIMARY *ppi, const AV1EncoderConfig *oxcf,
cpi->consec_zero_mv_alloc_size = consec_zero_mv_alloc_size;
cpi->mb_weber_stats = NULL;
+ cpi->mb_weber_stats_alloc_size = 0;
cpi->mb_delta_q = NULL;
+ cpi->mb_delta_q_alloc_size = 0;
cpi->palette_pixel_num = 0;
cpi->scaled_last_source_available = 0;
diff --git a/av1/encoder/encoder.h b/av1/encoder/encoder.h
index 82e9cab3fa..c0ee4e7353 100644
--- a/av1/encoder/encoder.h
+++ b/av1/encoder/encoder.h
@@ -3585,6 +3585,12 @@ typedef struct AV1_COMP {
*/
WeberStats *mb_weber_stats;
+ /*!
+ * Allocated memory size (number of elements) for |mb_weber_stats| (and
+ * |prep_rate_estimates|, |ext_rate_distribution|).
+ */
+ int mb_weber_stats_alloc_size;
+
/*!
* Buffer to store rate cost estimates for each macro block (8x8) in the
* preprocessing stage used in allintra mode.
@@ -3617,6 +3623,11 @@ typedef struct AV1_COMP {
*/
int *mb_delta_q;
+ /*!
+ * Allocated memory size (number of elements) for |mb_delta_q|.
+ */
+ int mb_delta_q_alloc_size;
+
/*!
* Flag to indicate that current frame is dropped.
*/
diff --git a/av1/encoder/encoder_alloc.h b/av1/encoder/encoder_alloc.h
index 97b5273a95..04a6e89742 100644
--- a/av1/encoder/encoder_alloc.h
+++ b/av1/encoder/encoder_alloc.h
@@ -365,6 +365,7 @@ static inline void dealloc_compressor_data(AV1_COMP *cpi) {
aom_free(cpi->mb_weber_stats);
cpi->mb_weber_stats = NULL;
+ cpi->mb_weber_stats_alloc_size = 0;
if (cpi->oxcf.enable_rate_guide_deltaq) {
aom_free(cpi->prep_rate_estimates);
@@ -376,6 +377,7 @@ static inline void dealloc_compressor_data(AV1_COMP *cpi) {
aom_free(cpi->mb_delta_q);
cpi->mb_delta_q = NULL;
+ cpi->mb_delta_q_alloc_size = 0;
#if !CONFIG_REALTIME_ONLY
av1_free_tpl_gop_stats(&cpi->extrc_tpl_gop_stats);
diff --git a/test/disflow_test.cc b/test/disflow_test.cc
index 30d402ad12..61226b59da 100644
--- a/test/disflow_test.cc
+++ b/test/disflow_test.cc
@@ -132,9 +132,8 @@ INSTANTIATE_TEST_SUITE_P(SVE, ComputeFlowTest,
#if CONFIG_AV1_ENCODER && !CONFIG_REALTIME_ONLY
TEST(DisflowTest, NarrowDimensions) {
- YV12_BUFFER_CONFIG src, ref;
- memset(&src, 0, sizeof(src));
- memset(&ref, 0, sizeof(ref));
+ YV12_BUFFER_CONFIG src = {};
+ YV12_BUFFER_CONFIG ref = {};
constexpr int kWidth = 17;
constexpr int kHeight = 1;
@@ -157,9 +156,8 @@ TEST(DisflowTest, NarrowDimensions) {
}
TEST(DisflowTest, MismatchedDimensions) {
- YV12_BUFFER_CONFIG src, ref;
- memset(&src, 0, sizeof(src));
- memset(&ref, 0, sizeof(ref));
+ YV12_BUFFER_CONFIG src = {};
+ YV12_BUFFER_CONFIG ref = {};
ASSERT_EQ(aom_alloc_frame_buffer(&src, 128, 96, 1, 1, 0, AOM_BORDER_IN_PIXELS,
0, true, 0),
@@ -180,9 +178,8 @@ TEST(DisflowTest, MismatchedDimensions) {
}
TEST(DisflowTest, MismatchedStrides) {
- YV12_BUFFER_CONFIG src, ref;
- memset(&src, 0, sizeof(src));
- memset(&ref, 0, sizeof(ref));
+ YV12_BUFFER_CONFIG src = {};
+ YV12_BUFFER_CONFIG ref = {};
constexpr int kWidth = 165;
constexpr int kHeight = 513;
diff --git a/test/encode_api_test.cc b/test/encode_api_test.cc
index cf8d636e19..578986b443 100644
--- a/test/encode_api_test.cc
+++ b/test/encode_api_test.cc
@@ -2741,6 +2741,57 @@ TEST(EncodeAPI, Buganizer558434716) {
aom_img_free(img_large);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
+
+TEST(EncodeAPI, PerceptualAIDynamicResolutionChange) {
+ aom_codec_iface_t *iface = aom_codec_av1_cx();
+ aom_codec_enc_cfg_t cfg;
+ ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_ALL_INTRA),
+ AOM_CODEC_OK);
+
+ cfg.g_w = 17;
+ cfg.g_h = 1;
+ cfg.g_forced_max_frame_width = 320;
+ cfg.g_forced_max_frame_height = 240;
+ cfg.g_pass = AOM_RC_ONE_PASS;
+ cfg.g_lag_in_frames = 0;
+ cfg.g_threads = 1;
+
+ aom_codec_ctx_t enc;
+ ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
+ ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_DELTAQ_MODE, 3), AOM_CODEC_OK);
+
+ aom_image_t *img_small = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 17, 1, 1);
+ ASSERT_NE(img_small, nullptr);
+ FillImage(img_small, 128);
+
+ aom_image_t *img_large =
+ aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 320, 240, 1);
+ ASSERT_NE(img_large, nullptr);
+ FillImage(img_large, 128);
+
+ // Frame 0 at 17x1
+ ASSERT_EQ(aom_codec_encode(&enc, img_small, 0, 1, 0), AOM_CODEC_OK);
+
+ // Switch to larger resolution
+ cfg.g_w = 320;
+ cfg.g_h = 240;
+ ASSERT_EQ(aom_codec_enc_config_set(&enc, &cfg), AOM_CODEC_OK);
+
+ // Frame 1 at 320x240
+ ASSERT_EQ(aom_codec_encode(&enc, img_large, 1, 1, 0), AOM_CODEC_OK);
+
+ // Switch back to 17x1
+ cfg.g_w = 17;
+ cfg.g_h = 1;
+ ASSERT_EQ(aom_codec_enc_config_set(&enc, &cfg), AOM_CODEC_OK);
+
+ // Frame 2 at 17x1
+ ASSERT_EQ(aom_codec_encode(&enc, img_small, 2, 1, 0), AOM_CODEC_OK);
+
+ aom_img_free(img_small);
+ aom_img_free(img_large);
+ ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
+}
#endif // !CONFIG_REALTIME_ONLY
} // namespace