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 07ea924..419884b 100644 --- a/aom_dsp/flow_estimation/corner_match.c +++ b/aom_dsp/flow_estimation/corner_match.c
@@ -290,6 +290,10 @@ 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 a1d7491..cc1b56c 100644 --- a/aom_dsp/flow_estimation/disflow.c +++ b/aom_dsp/flow_estimation/disflow.c
@@ -780,18 +780,32 @@ *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 4fdad0b..7b1af9c 100644 --- a/av1/encoder/global_motion_facade.c +++ b/av1/encoder/global_motion_facade.c
@@ -314,6 +314,7 @@ 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 d881f7b..30d402a 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 @@ ::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