Commit 8fd7c59010 for aom

commit 8fd7c5901039ea1c32b3d86ce1084d4a4219e371
Author: Alejandro Pacheco <alepacheco99@gmail.com>
Date:   Tue Oct 20 00:22:58 2026 +0200

    Do not apply an ROI map to a frame of another size

    av1_set_roi_map() checks that the map matches the frame size when the
    map is set, but av1_apply_roi_map() copies mi_rows * mi_cols bytes from
    it for every later frame. After a larger frame size is set with
    aom_codec_enc_config_set(), the copy reads past the end of the map
    (heap buffer overflow).

    Disable ROI for a frame whose size does not match the map, as is done
    when ROI is not supported for the frame. The check comes after the
    active map check, so that it does not disable the segmentation an
    active map uses.

    The new tests set an ROI map and then grow the frame size, with and
    without a forced maximum frame size. ASan reports a heap buffer
    overflow in av1_apply_roi_map() without the fix.

    Bug: 573358005
    Change-Id: I1b3baf5aa2ad804b715a71629b336ac08cec3dac

diff --git a/av1/encoder/encoder_utils.c b/av1/encoder/encoder_utils.c
index 6c4c85aa5d..d45d4eb039 100644
--- a/av1/encoder/encoder_utils.c
+++ b/av1/encoder/encoder_utils.c
@@ -448,6 +448,19 @@ void av1_apply_roi_map(AV1_COMP *cpi) {
     return;
   }

+  // The ROI map is sized for the frame dimensions at the time it was set
+  // (av1_set_roi_map() requires an exact match). If this frame has a different
+  // size (e.g. after a resize or for another spatial layer), the map does not
+  // describe it, so disable ROI.
+  if ((int)roi->rows != cm->mi_params.mi_rows ||
+      (int)roi->cols != cm->mi_params.mi_cols) {
+    av1_disable_segmentation(seg);
+    roi->enabled = 0;
+    roi->delta_qp_enabled = 0;
+    roi->reference_enabled = 0;
+    return;
+  }
+
   memcpy(&ref_frame, roi->ref_frame, sizeof(ref_frame));
   roi->reference_enabled = 0;
   roi->delta_qp_enabled = 0;
diff --git a/test/roi_map_test.cc b/test/roi_map_test.cc
index cfca18f598..4da3c95c42 100644
--- a/test/roi_map_test.cc
+++ b/test/roi_map_test.cc
@@ -243,4 +243,72 @@ TEST_P(ROIMapThreadTest, SameOutput) {

 AV1_INSTANTIATE_TEST_SUITE(ROIMapThreadTest, ::testing::Values(9));

+// The ROI map is sized for the frame size at the time AOME_SET_ROI_MAP is
+// called. A later frame size increase through aom_codec_enc_config_set() must
+// not read past the end of the map. With a forced maximum frame size the
+// increase happens after a frame has been encoded; without one it has to
+// happen before the first frame, while the sequence header can still change.
+void RunROIMapUpscaleTest(bool forced_max) {
+  constexpr unsigned int kWidth = 320;
+  constexpr unsigned int kHeight = 180;
+  constexpr unsigned int kNewWidth = 1280;
+  constexpr unsigned int kNewHeight = 720;
+  aom_codec_iface_t *iface = aom_codec_av1_cx();
+  aom_codec_enc_cfg_t cfg;
+  ASSERT_EQ(AOM_CODEC_OK,
+            aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME));
+  cfg.g_w = kWidth;
+  cfg.g_h = kHeight;
+  cfg.g_lag_in_frames = 0;
+  if (forced_max) {
+    cfg.g_forced_max_frame_width = kNewWidth;
+    cfg.g_forced_max_frame_height = kNewHeight;
+  }
+  aom_codec_ctx_t enc;
+  ASSERT_EQ(AOM_CODEC_OK, aom_codec_enc_init(&enc, iface, &cfg, 0));
+  ASSERT_EQ(AOM_CODEC_OK, aom_codec_control(&enc, AOME_SET_CPUUSED, 9));
+
+  aom_image_t *image =
+      aom_img_alloc(nullptr, AOM_IMG_FMT_I420, kNewWidth, kNewHeight, 1);
+  ASSERT_NE(image, nullptr);
+  memset(image->img_data, 128, image->sz);
+  ASSERT_EQ(aom_img_set_rect(image, 0, 0, kWidth, kHeight, 0), 0);
+  aom_codec_pts_t pts = 0;
+  if (forced_max) {
+    ASSERT_EQ(AOM_CODEC_OK, aom_codec_encode(&enc, image, pts++, 1, 0));
+  }
+
+  // The map dimensions must match the frame size in 4x4 units, with the frame
+  // height and width aligned to a multiple of 8.
+  aom_roi_map_t roi = aom_roi_map_t();
+  roi.rows = ((kHeight + 7) & ~7u) / 4;
+  roi.cols = ((kWidth + 7) & ~7u) / 4;
+  std::vector<uint8_t> map(roi.rows * roi.cols, 1);
+  roi.enabled = 1;
+  roi.roi_map = map.data();
+  roi.delta_q[1] = -20;
+  memset(roi.ref_frame, -1, sizeof(roi.ref_frame));
+  ASSERT_EQ(AOM_CODEC_OK, aom_codec_control(&enc, AOME_SET_ROI_MAP, &roi));
+  if (forced_max) {
+    ASSERT_EQ(AOM_CODEC_OK, aom_codec_encode(&enc, image, pts++, 1, 0));
+  }
+
+  cfg.g_w = kNewWidth;
+  cfg.g_h = kNewHeight;
+  ASSERT_EQ(AOM_CODEC_OK, aom_codec_enc_config_set(&enc, &cfg));
+  ASSERT_EQ(aom_img_set_rect(image, 0, 0, kNewWidth, kNewHeight, 0), 0);
+  ASSERT_EQ(AOM_CODEC_OK, aom_codec_encode(&enc, image, pts++, 1, 0));
+  ASSERT_EQ(AOM_CODEC_OK, aom_codec_encode(&enc, image, pts++, 1, 0));
+
+  ASSERT_EQ(AOM_CODEC_OK, aom_codec_encode(&enc, nullptr, 0, 0, 0));
+  aom_img_free(image);
+  ASSERT_EQ(AOM_CODEC_OK, aom_codec_destroy(&enc));
+}
+
+TEST(ROIMapUpscaleTest, WithForcedMaxFrameSize) { RunROIMapUpscaleTest(true); }
+
+TEST(ROIMapUpscaleTest, WithoutForcedMaxFrameSize) {
+  RunROIMapUpscaleTest(false);
+}
+
 }  // namespace