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