Commit b5f92a8454 for aom
commit b5f92a845464db51123572b9b52fcabfc530e06b
Author: Lin Zheng <linzhen@google.com>
Date: Thu Sep 10 00:06:05 2026 +0000
Disallow global motion estimation on mismatched frame strides
In the case of dynamic resolution changes, the source frame and
reference frames may have identical crop dimensions but different
border paddings and strides.
Because optical flow computation takes a single stride parameter for both
buffers, evaluating optical flow across buffers with mismatched strides
causes out-of-bounds row indexing and memory corruption.
This patch:
1. Adds stride checks in av1_compute_global_motion_disflow and
av1_compute_global_motion_feature_match to safely return false when
layer 0 strides differ.
2. Checks ref_buf[frame]->y_stride == cpi->source->y_stride in
update_valid_ref_frames_for_gm() so mismatched reference frames
are skipped for global motion search.
3. Adds a unit test in DisflowTest.MismatchedStrides covering both
disflow and corner match algorithms.
Bug: aomedia:559075256
Change-Id: If79b7d8705bb839c88e1d2f2d168af993b7a8b77
diff --git a/aom_dsp/flow_estimation/corner_match.c b/aom_dsp/flow_estimation/corner_match.c
index 07ea924e8e..419884b8e6 100644
--- a/aom_dsp/flow_estimation/corner_match.c
+++ b/aom_dsp/flow_estimation/corner_match.c
@@ -290,6 +290,10 @@ 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) {
+ return false;
+ }
+
// find correspondences between the two images
correspondences = (Correspondence *)aom_malloc(src_corners->num_corners *
sizeof(*correspondences));
diff --git a/aom_dsp/flow_estimation/disflow.c b/aom_dsp/flow_estimation/disflow.c
index a1d74914ab..cc1b56c843 100644
--- a/aom_dsp/flow_estimation/disflow.c
+++ b/aom_dsp/flow_estimation/disflow.c
@@ -780,18 +780,32 @@ bool av1_compute_global_motion_disflow(
*mem_alloc_failed = true;
return false;
}
+
+ if (src_layers != ref_layers) {
+ return false;
+ }
+
if (!av1_compute_corner_list(src, bit_depth, downsample_level, src_corners)) {
*mem_alloc_failed = true;
return false;
}
- assert(src_layers == ref_layers);
-
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].stride != src_pyramid->layers[0].stride) {
+ return false;
+ }
+ if (src_width < (1 << DOWNSAMPLE_SHIFT) ||
+ src_height < (1 << DOWNSAMPLE_SHIFT)) {
+ return false;
+ }
+ if (src_corners->num_corners == 0) {
+ return false;
+ }
+
FlowField *flow = alloc_flow_field(src_width, src_height);
if (!flow) {
*mem_alloc_failed = true;
diff --git a/av1/encoder/global_motion_facade.c b/av1/encoder/global_motion_facade.c
index 4fdad0bedb..7b1af9ca7f 100644
--- a/av1/encoder/global_motion_facade.c
+++ b/av1/encoder/global_motion_facade.c
@@ -314,6 +314,7 @@ static inline void update_valid_ref_frames_for_gm(
if (ref_buf[frame]->y_crop_width == cpi->source->y_crop_width &&
ref_buf[frame]->y_crop_height == cpi->source->y_crop_height &&
+ ref_buf[frame]->y_stride == cpi->source->y_stride &&
do_gm_search_logic(&cpi->sf, frame) && !prune_ref_frames &&
ref_pyr_lvl <= pyr_lvl && !cur_frame_gm_disabled) {
assert(ref_buf[frame] != NULL);
diff --git a/test/disflow_test.cc b/test/disflow_test.cc
index d881f7baa4..30d402ad12 100644
--- a/test/disflow_test.cc
+++ b/test/disflow_test.cc
@@ -9,6 +9,7 @@
* PATENTS file, you can obtain it at www.aomedia.org/license/patent.
*/
+#include "aom_dsp/flow_estimation/corner_match.h"
#include "aom_dsp/flow_estimation/disflow.h"
#include "gtest/gtest.h"
@@ -129,4 +130,85 @@ INSTANTIATE_TEST_SUITE_P(SVE, ComputeFlowTest,
::testing::Values(aom_compute_flow_at_point_sve));
#endif
+#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));
+
+ constexpr int kWidth = 17;
+ constexpr int kHeight = 1;
+ ASSERT_EQ(aom_alloc_frame_buffer(&src, kWidth, kHeight, 1, 1, 0,
+ AOM_BORDER_IN_PIXELS, 0, true, 0),
+ 0);
+ ASSERT_EQ(aom_alloc_frame_buffer(&ref, kWidth, kHeight, 1, 1, 0,
+ AOM_BORDER_IN_PIXELS, 0, true, 0),
+ 0);
+
+ MotionModel motion_models[1];
+ bool mem_alloc_failed = false;
+ bool ret = av1_compute_global_motion_disflow(
+ TRANSLATION, &src, &ref, 8, 0, motion_models, 1, &mem_alloc_failed);
+ EXPECT_FALSE(ret);
+ EXPECT_FALSE(mem_alloc_failed);
+
+ aom_free_frame_buffer(&src);
+ aom_free_frame_buffer(&ref);
+}
+
+TEST(DisflowTest, MismatchedDimensions) {
+ YV12_BUFFER_CONFIG src, ref;
+ memset(&src, 0, sizeof(src));
+ memset(&ref, 0, sizeof(ref));
+
+ ASSERT_EQ(aom_alloc_frame_buffer(&src, 128, 96, 1, 1, 0, AOM_BORDER_IN_PIXELS,
+ 0, true, 0),
+ 0);
+ ASSERT_EQ(aom_alloc_frame_buffer(&ref, 320, 240, 1, 1, 0,
+ AOM_BORDER_IN_PIXELS, 0, true, 0),
+ 0);
+
+ MotionModel motion_models[1];
+ bool mem_alloc_failed = false;
+ bool ret = av1_compute_global_motion_disflow(
+ TRANSLATION, &src, &ref, 8, 0, motion_models, 1, &mem_alloc_failed);
+ EXPECT_FALSE(ret);
+ EXPECT_FALSE(mem_alloc_failed);
+
+ aom_free_frame_buffer(&src);
+ aom_free_frame_buffer(&ref);
+}
+
+TEST(DisflowTest, MismatchedStrides) {
+ YV12_BUFFER_CONFIG src, ref;
+ memset(&src, 0, sizeof(src));
+ memset(&ref, 0, sizeof(ref));
+
+ constexpr int kWidth = 165;
+ constexpr int kHeight = 513;
+ ASSERT_EQ(aom_alloc_frame_buffer(&src, kWidth, kHeight, 1, 1, 0,
+ AOM_BORDER_IN_PIXELS, 0, true, 0),
+ 0);
+ ASSERT_EQ(
+ aom_alloc_frame_buffer(&ref, kWidth, kHeight, 1, 1, 0, 96, 0, true, 0),
+ 0);
+ EXPECT_NE(src.y_stride, ref.y_stride);
+
+ MotionModel motion_models[1];
+ bool mem_alloc_failed = false;
+ bool ret = av1_compute_global_motion_disflow(
+ TRANSLATION, &src, &ref, 8, 0, motion_models, 1, &mem_alloc_failed);
+ EXPECT_FALSE(ret);
+ EXPECT_FALSE(mem_alloc_failed);
+
+ ret = av1_compute_global_motion_feature_match(
+ TRANSLATION, &src, &ref, 8, 0, motion_models, 1, &mem_alloc_failed);
+ EXPECT_FALSE(ret);
+ EXPECT_FALSE(mem_alloc_failed);
+
+ aom_free_frame_buffer(&src);
+ aom_free_frame_buffer(&ref);
+}
+#endif // CONFIG_AV1_ENCODER && !CONFIG_REALTIME_ONLY
+
} // namespace