CRASH in MediaPlayerPrivateMediaSourceAVFObjC::addAudioRenderer(), uncaught ObjC exception
https://bugs.webkit.org/show_bug.cgi?id=209827
<rdar://problem/61113080>

Reviewed by Eric Carlson.

-[AVSampleBufferAudioRenderer init] can, in exceptional conditions, return nil. Passing a
nil object, or another object that AVSampleBufferRenderSynchronizer considers "invalid", into
-[AVSampleBufferRenderSynchronizer addRenderer:] will throw an exception. Protect against this
scenario in two ways:

- Check the return value of -[AVSampleBufferAudioRenderer init], and if nil, log an error,
  log to console, and set the network state to "DecodeError".
- Wrap calls to -addRenderer: in @try/@catch blocks, which if caught, log an error, assert,
  and set the network state to "DecodeError".

* Modules/mediasource/MediaSource.cpp:
(WebCore::MediaSource::failedToCreateRenderer):
* Modules/mediasource/MediaSource.h:
* platform/graphics/MediaSourcePrivateClient.h:
* platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm:
(WebCore::MediaPlayerPrivateMediaSourceAVFObjC::ensureLayer):
* platform/graphics/avfoundation/objc/MediaSourcePrivateAVFObjC.h:
* platform/graphics/avfoundation/objc/MediaSourcePrivateAVFObjC.mm:
(WebCore::MediaSourcePrivateAVFObjC::failedToCreateAudioRenderer):
(WebCore::MediaSourcePrivateAVFObjC::failedToCreateVideoRenderer):
* platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.mm:
(WebCore::SourceBufferPrivateAVFObjC::trackDidChangeEnabled):


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@259363 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 2f22d49..0c4d86c 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,34 @@
+2020-04-01  Jer Noble  <jer.noble@apple.com>
+
+        CRASH in MediaPlayerPrivateMediaSourceAVFObjC::addAudioRenderer(), uncaught ObjC exception
+        https://bugs.webkit.org/show_bug.cgi?id=209827
+        <rdar://problem/61113080>
+
+        Reviewed by Eric Carlson.
+
+        -[AVSampleBufferAudioRenderer init] can, in exceptional conditions, return nil. Passing a
+        nil object, or another object that AVSampleBufferRenderSynchronizer considers "invalid", into
+        -[AVSampleBufferRenderSynchronizer addRenderer:] will throw an exception. Protect against this
+        scenario in two ways:
+
+        - Check the return value of -[AVSampleBufferAudioRenderer init], and if nil, log an error,
+          log to console, and set the network state to "DecodeError".
+        - Wrap calls to -addRenderer: in @try/@catch blocks, which if caught, log an error, assert,
+          and set the network state to "DecodeError".
+
+        * Modules/mediasource/MediaSource.cpp:
+        (WebCore::MediaSource::failedToCreateRenderer):
+        * Modules/mediasource/MediaSource.h:
+        * platform/graphics/MediaSourcePrivateClient.h:
+        * platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm:
+        (WebCore::MediaPlayerPrivateMediaSourceAVFObjC::ensureLayer):
+        * platform/graphics/avfoundation/objc/MediaSourcePrivateAVFObjC.h:
+        * platform/graphics/avfoundation/objc/MediaSourcePrivateAVFObjC.mm:
+        (WebCore::MediaSourcePrivateAVFObjC::failedToCreateAudioRenderer):
+        (WebCore::MediaSourcePrivateAVFObjC::failedToCreateVideoRenderer):
+        * platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.mm:
+        (WebCore::SourceBufferPrivateAVFObjC::trackDidChangeEnabled):
+
 2020-04-01  Chris Dumez  <cdumez@apple.com>
 
         ASSERTION FAILED: m_wrapper on imported/w3c/web-platform-tests/html/semantics/embedded-content/media-elements/ready-states/autoplay.html
diff --git a/Source/WebCore/Modules/mediasource/MediaSource.cpp b/Source/WebCore/Modules/mediasource/MediaSource.cpp
index 4d1aca5..99f7fd0 100644
--- a/Source/WebCore/Modules/mediasource/MediaSource.cpp
+++ b/Source/WebCore/Modules/mediasource/MediaSource.cpp
@@ -1093,6 +1093,12 @@
 }
 #endif
 
+void MediaSource::failedToCreateRenderer(RendererType type)
+{
+    if (auto context = scriptExecutionContext())
+        context->addConsoleMessage(MessageSource::JS, MessageLevel::Error, makeString("MediaSource ", type == RendererType::Video ? "video" : "audio", " renderer creation failed."));
+}
+
 }
 
 #endif
diff --git a/Source/WebCore/Modules/mediasource/MediaSource.h b/Source/WebCore/Modules/mediasource/MediaSource.h
index 83d1c4f..71dc06c 100644
--- a/Source/WebCore/Modules/mediasource/MediaSource.h
+++ b/Source/WebCore/Modules/mediasource/MediaSource.h
@@ -122,6 +122,8 @@
     void setLogIdentifier(const void*) final;
 #endif
 
+    void failedToCreateRenderer(RendererType) final;
+
 private:
     explicit MediaSource(ScriptExecutionContext&);
 
diff --git a/Source/WebCore/platform/graphics/MediaSourcePrivateClient.h b/Source/WebCore/platform/graphics/MediaSourcePrivateClient.h
index 1941382..3fe2ad4 100644
--- a/Source/WebCore/platform/graphics/MediaSourcePrivateClient.h
+++ b/Source/WebCore/platform/graphics/MediaSourcePrivateClient.h
@@ -50,6 +50,9 @@
 #if !RELEASE_LOG_DISABLED
     virtual void setLogIdentifier(const void*) = 0;
 #endif
+
+    enum class RendererType { Audio, Video };
+    virtual void failedToCreateRenderer(RendererType) = 0;
 };
 
 }
diff --git a/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm b/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm
index b42f491..38696dc 100644
--- a/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm
+++ b/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm
@@ -766,6 +766,14 @@
     [m_sampleBufferDisplayLayer setName:@"MediaPlayerPrivateMediaSource AVSampleBufferDisplayLayer"];
 #endif
 
+    if (!m_sampleBufferDisplayLayer) {
+        ERROR_LOG(LOGIDENTIFIER, "Failed to create AVSampleBufferDisplayLayer");
+        if (m_mediaSourcePrivate)
+            m_mediaSourcePrivate->failedToCreateRenderer(MediaSourcePrivateAVFObjC::RendererType::Video);
+        setNetworkState(MediaPlayer::NetworkState::DecodeError);
+        return;
+    }
+
 #if HAVE(AVSAMPLEBUFFERVIDEOOUTPUT)
     ASSERT(!m_videoOutput);
     if (isVideoOutputAvailable()) {
@@ -775,17 +783,19 @@
     }
 #endif
 
-    ASSERT(m_sampleBufferDisplayLayer);
-    if (!m_sampleBufferDisplayLayer) {
-        ERROR_LOG(LOGIDENTIFIER, "Failed to create AVSampleBufferDisplayLayer");
+    if ([m_sampleBufferDisplayLayer respondsToSelector:@selector(setPreventsDisplaySleepDuringVideoPlayback:)])
+        m_sampleBufferDisplayLayer.get().preventsDisplaySleepDuringVideoPlayback = NO;
+
+    @try {
+        [m_synchronizer addRenderer:m_sampleBufferDisplayLayer.get()];
+    } @catch(NSException *exception) {
+        ERROR_LOG(LOGIDENTIFIER, "-[AVSampleBufferRenderSynchronizer addRenderer:] threw an exception: ", [[exception name] UTF8String], ", reason : ", [[exception reason] UTF8String]);
+        ASSERT_NOT_REACHED();
+
         setNetworkState(MediaPlayer::NetworkState::DecodeError);
         return;
     }
 
-    if ([m_sampleBufferDisplayLayer respondsToSelector:@selector(setPreventsDisplaySleepDuringVideoPlayback:)])
-        m_sampleBufferDisplayLayer.get().preventsDisplaySleepDuringVideoPlayback = NO;
-
-    [m_synchronizer addRenderer:m_sampleBufferDisplayLayer.get()];
     if (m_mediaSourcePrivate)
         m_mediaSourcePrivate->setVideoLayer(m_sampleBufferDisplayLayer.get());
     m_videoLayerManager->setVideoLayer(m_sampleBufferDisplayLayer.get(), snappedIntRect(m_player->playerContentBoxRect()).size());
@@ -1153,6 +1163,11 @@
 void MediaPlayerPrivateMediaSourceAVFObjC::addAudioRenderer(AVSampleBufferAudioRenderer* audioRenderer)
 ALLOW_NEW_API_WITHOUT_GUARDS_END
 {
+    if (!audioRenderer) {
+        ASSERT_NOT_REACHED();
+        return;
+    }
+
     if (!m_sampleBufferAudioRendererMap.add((__bridge CFTypeRef)audioRenderer, AudioRendererProperties()).isNewEntry)
         return;
 
@@ -1160,7 +1175,15 @@
     [audioRenderer setVolume:m_player->volume()];
     [audioRenderer setAudioTimePitchAlgorithm:(m_player->preservesPitch() ? AVAudioTimePitchAlgorithmSpectral : AVAudioTimePitchAlgorithmVarispeed)];
 
-    [m_synchronizer addRenderer:audioRenderer];
+    @try {
+        [m_synchronizer addRenderer:audioRenderer];
+    } @catch(NSException *exception) {
+        ERROR_LOG(LOGIDENTIFIER, "-[AVSampleBufferRenderSynchronizer addRenderer:] threw an exception: ", [[exception name] UTF8String], ", reason : ", [[exception reason] UTF8String]);
+        ASSERT_NOT_REACHED();
+
+        setNetworkState(MediaPlayer::NetworkState::DecodeError);
+        return;
+    }
     m_player->renderingModeChanged();
 }
 
diff --git a/Source/WebCore/platform/graphics/avfoundation/objc/MediaSourcePrivateAVFObjC.h b/Source/WebCore/platform/graphics/avfoundation/objc/MediaSourcePrivateAVFObjC.h
index e421cef..e1e4aa7 100644
--- a/Source/WebCore/platform/graphics/avfoundation/objc/MediaSourcePrivateAVFObjC.h
+++ b/Source/WebCore/platform/graphics/avfoundation/objc/MediaSourcePrivateAVFObjC.h
@@ -29,6 +29,7 @@
 #if ENABLE(MEDIA_SOURCE) && USE(AVFOUNDATION)
 
 #include "MediaSourcePrivate.h"
+#include "MediaSourcePrivateClient.h"
 #include <wtf/Deque.h>
 #include <wtf/LoggerHelper.h>
 #include <wtf/RefPtr.h>
@@ -110,6 +111,9 @@
     const void* nextSourceBufferLogIdentifier() { return childLogIdentifier(m_logIdentifier, ++m_nextSourceBufferID); }
 #endif
 
+    using RendererType = MediaSourcePrivateClient::RendererType;
+    void failedToCreateRenderer(RendererType);
+
 private:
     MediaSourcePrivateAVFObjC(MediaPlayerPrivateMediaSourceAVFObjC*, MediaSourcePrivateClient*);
 
diff --git a/Source/WebCore/platform/graphics/avfoundation/objc/MediaSourcePrivateAVFObjC.mm b/Source/WebCore/platform/graphics/avfoundation/objc/MediaSourcePrivateAVFObjC.mm
index 0e7ae05..eac444c 100644
--- a/Source/WebCore/platform/graphics/avfoundation/objc/MediaSourcePrivateAVFObjC.mm
+++ b/Source/WebCore/platform/graphics/avfoundation/objc/MediaSourcePrivateAVFObjC.mm
@@ -325,6 +325,11 @@
 }
 #endif
 
+void MediaSourcePrivateAVFObjC::failedToCreateRenderer(RendererType type)
+{
+    m_client->failedToCreateRenderer(type);
+}
+
 }
 
 #endif // ENABLE(MEDIA_SOURCE) && USE(AVFOUNDATION)
diff --git a/Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.mm b/Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.mm
index b2f1db6..4909fdd 100644
--- a/Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.mm
+++ b/Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.mm
@@ -897,6 +897,15 @@
         ALLOW_NEW_API_WITHOUT_GUARDS_END
         if (!m_audioRenderers.contains(trackID)) {
             renderer = adoptNS([PAL::allocAVSampleBufferAudioRendererInstance() init]);
+
+            if (!renderer) {
+                ERROR_LOG(LOGIDENTIFIER, "-[AVSampleBufferAudioRenderer init] returned nil! bailing!");
+                if (m_mediaSource)
+                    m_mediaSource->failedToCreateRenderer(MediaSourcePrivateAVFObjC::RendererType::Audio);
+                m_mediaSource->player()->setNetworkState(MediaPlayer::NetworkState::DecodeError);
+                return;
+            }
+
             auto weakThis = makeWeakPtr(*this);
             [renderer requestMediaDataWhenReadyOnQueue:dispatch_get_main_queue() usingBlock:^{
                 if (weakThis)