← All issues

[CoreIPC] [GPUP] off-main-thread ~RemoteAudioMediaStreamTrackRendererInternalUnitManagerUnit mutates unlocked AudioSession interruption-observer WeakHashSet

a9c37a1

LayoutTests/fast/mediastream/delete-audio-unit.html

promise_test(async () => {
const context = new AudioContext();
const oscillator = context.createOscillator();
const streamDestination = context.createMediaStreamDestination();
oscillator.connect(streamDestination);
oscillator.start();
 
video.srcObject = streamDestination.stream;
await video.play();
await new Promise(resolve => setTimeout(resolve, 100));
 
internals?.deleteAudioUnit(); // triggers IPC delete while unit is running
internals?.setPageMediaVolume(0);
await new Promise(resolve => setTimeout(resolve, 100));
}, "Delete a running audio unit without stopping it");

Source/WebCore/platform/mediastream/cocoa/AudioMediaStreamTrackRendererUnit.cpp

+void AudioMediaStreamTrackRendererUnit::Unit::deleteUnitForTesting()
+{
+ assertIsMainThread();
+ m_internalUnit->deleteUnitForTesting();
+}

WebKit의 GPU process는 RemoteAudioMediaStreamTrackRendererInternalUnitManager를 호스팅합니다. 이 컴포넌트는 WebProcess를 대신해 audio rendering unit을 관리합니다. 각 unit은 real-time rendering thread에서 audio rendering callback을 실행하며, 생성·소멸·observer 등록 등 lifecycle 관련 작업은 반드시 main thread에서만 수행되어야 합니다. AudioSession은 interruption observer를 WeakHashSet으로 관리하는데, 이 구조는 thread-safe하지 않습니다.

이 commit은 thread-safety race를 수정합니다. 실행 중인 audio unit을 IPC를 통해 삭제하면 destructor가 rendering thread에서 실행될 수 있었고, 이로 인해 WeakHashSet이 보호 없이 변경되는 문제가 있었습니다.

IPC로 유발된 삭제 경로에서는 어느 thread가 마지막 reference를 해제할지 보장되지 않았습니다. rendering thread가 활성 상태인 경우, destructor와 AudioSession::removeObserver() 호출이 해당 thread에서 실행되면서 동시에 접근하는 main thread와 race condition이 발생했습니다.

수정은 unit이 해제되기 전에 반드시 중단되도록 보장합니다. 이를 통해 rendering thread가 마지막 reference를 보유하지 않도록 합니다. 아울러 새로운 internals API deleteAudioUnit()을 통해 테스트에서 race condition을 재현할 수 있게 되었습니다.

BEFORE (racy):
WebProcess         GPUProcess main thread       Rendering thread
    │                      │                           │
    ├──IPC: deleteUnit────►│                           │  ← unit still running
    │              remove unit from map                │
    │              (main drops its ref)                │
    │                                        unit dtor fires here
    │                                   ──► AudioSession::removeObserver()
    │                                        [unlocked WeakHashSet write]

AFTER (fixed):
WebProcess         GPUProcess main thread       Rendering thread
    │                      │                           │
    ├──IPC: deleteUnit────►│                           │
    │                 unit->stop()                     │
    │                 (rendering thread releases ref)  │
    │              remove unit from map                │
    │              (last ref drops on main thread)     │
    │              unit dtor fires on main thread      │
    │         ──► AudioSession::removeObserver() [safe]│

GPU process 내에서 web을 통해 도달 가능한 race condition입니다. 공격자가 제어하는 MediaStream으로 IPC timing을 조작하면 취약한 코드 경로를 유발할 수 있으며, 잘못된 thread에서 AudioSession observer map이 손상될 가능성이 있습니다.

이 수정은 ref-counting 규율에 의존합니다. unit을 중단하면 rendering thread가 IPC 측 해제보다 먼저 reference를 해제해야 합니다. stop() 호출이 rendering thread의 ref-drop에 대해 동기적으로 보장되는지, 아니면 여전히 race window가 남아있는지 확인해야 합니다.

동일한 ownership 패턴이 audio stack 내 다른 observer 등록 및 IPC 유발 lifecycle 변경에도 적용됩니다. AudioSession observer 등록·해제 호출 지점에서 유사한 off-thread destructor 경로가 있는지 점검하는 것이 유용합니다.

새로운 deleteAudioUnit() internals hook을 활용하면 fuzzing 과정에서 이 race condition을 더 안정적으로 재현할 수 있습니다.


포인터의 constant-folding을 방지합니다. WebAssembly.Memory.grow()가 vector를 재할당한 이후 stale 포인터로 out-of-bounds 쓰기가 가능합니다.