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