Mute MediaElementSourceNode when tainted.
authorjer.noble@apple.com <jer.noble@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 8 May 2018 21:32:03 +0000 (21:32 +0000)
committerjer.noble@apple.com <jer.noble@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 8 May 2018 21:32:03 +0000 (21:32 +0000)
https://bugs.webkit.org/show_bug.cgi?id=184866

Reviewed by Eric Carlson.

Source/WebCore:

Test: http/tests/security/webaudio-render-remote-audio-blocked-no-crossorigin.html

* Modules/webaudio/AudioContext.cpp:
(WebCore::AudioContext::wouldTaintOrigin const):
* Modules/webaudio/AudioContext.h:
* Modules/webaudio/MediaElementAudioSourceNode.cpp:
(WebCore::MediaElementAudioSourceNode::setFormat):
(WebCore::MediaElementAudioSourceNode::wouldTaintOrigin):
(WebCore::MediaElementAudioSourceNode::process):
* Modules/webaudio/MediaElementAudioSourceNode.h:

LayoutTests:

* http/tests/media/resources/1000Hz-sin.wav: Added.
* http/tests/security/webaudio-render-remote-audio-allowed-crossorigin-expected.txt: Added.
* http/tests/security/webaudio-render-remote-audio-allowed-crossorigin.html: Added.
* http/tests/security/webaudio-render-remote-audio-blocked-no-crossorigin-expected.txt: Added.
* http/tests/security/webaudio-render-remote-audio-blocked-no-crossorigin.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@231513 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/http/tests/media/resources/1000Hz-sin.wav [new file with mode: 0644]
LayoutTests/http/tests/security/webaudio-render-remote-audio-allowed-crossorigin-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/security/webaudio-render-remote-audio-allowed-crossorigin.html [new file with mode: 0644]
LayoutTests/http/tests/security/webaudio-render-remote-audio-blocked-no-crossorigin-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/security/webaudio-render-remote-audio-blocked-no-crossorigin.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/Modules/webaudio/AudioContext.cpp
Source/WebCore/Modules/webaudio/AudioContext.h
Source/WebCore/Modules/webaudio/MediaElementAudioSourceNode.cpp
Source/WebCore/Modules/webaudio/MediaElementAudioSourceNode.h

index 48114ec..6a4bdcb 100644 (file)
@@ -1,3 +1,16 @@
+2018-05-08  Jer Noble  <jer.noble@apple.com>
+
+        Mute MediaElementSourceNode when tainted.
+        https://bugs.webkit.org/show_bug.cgi?id=184866
+
+        Reviewed by Eric Carlson.
+
+        * http/tests/media/resources/1000Hz-sin.wav: Added.
+        * http/tests/security/webaudio-render-remote-audio-allowed-crossorigin-expected.txt: Added.
+        * http/tests/security/webaudio-render-remote-audio-allowed-crossorigin.html: Added.
+        * http/tests/security/webaudio-render-remote-audio-blocked-no-crossorigin-expected.txt: Added.
+        * http/tests/security/webaudio-render-remote-audio-blocked-no-crossorigin.html: Added.
+
 2018-05-08  Daniel Bates  <dabates@apple.com>
 
         Skip test http/tests/appcache/x-frame-options-prevents-framing.php for now in WebKit2.
diff --git a/LayoutTests/http/tests/media/resources/1000Hz-sin.wav b/LayoutTests/http/tests/media/resources/1000Hz-sin.wav
new file mode 100644 (file)
index 0000000..da9844f
Binary files /dev/null and b/LayoutTests/http/tests/media/resources/1000Hz-sin.wav differ
diff --git a/LayoutTests/http/tests/security/webaudio-render-remote-audio-allowed-crossorigin-expected.txt b/LayoutTests/http/tests/security/webaudio-render-remote-audio-allowed-crossorigin-expected.txt
new file mode 100644 (file)
index 0000000..76982a2
--- /dev/null
@@ -0,0 +1,10 @@
+Ensure that audio is rendered when tainted by a remote audio resource when CORS is enabled.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS outputArray is not silentArray
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/security/webaudio-render-remote-audio-allowed-crossorigin.html b/LayoutTests/http/tests/security/webaudio-render-remote-audio-allowed-crossorigin.html
new file mode 100644 (file)
index 0000000..d05e7f6
--- /dev/null
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script src="../../../resources/js-test-pre.js"></script>
+    <script src="../../media-resources/media-file.js"></script>
+</head>
+<body>
+<pre id="console"></pre>
+<script>
+    description("Ensure that audio is rendered when tainted by a remote audio resource when CORS is enabled.");
+    window.jsTestIsAsync = true;
+
+    function go() {
+        let audio = new Audio();
+        audio.crossOrigin = "anonymous";
+        let mediaFile = findMediaFile("audio", "../../media/resources/1000Hz-sin");
+        let type = mimeTypeForExtension(mediaFile.split('.').pop());
+        audio.src = "http://localhost:8080/security/resources/video-cross-origin-allow.php?name=" + mediaFile + "&type=" + type;
+
+        context = new webkitAudioContext();
+        let mediaSource = context.createMediaElementSource(audio);
+        let analyser = context.createAnalyser();
+        analyser.fftSize = 32;
+
+        mediaSource.connect(analyser);
+
+        context.resume().then(() => {
+            audio.play();
+        });
+
+        window.outputArray = new Float32Array(analyser.frequencyBinCount);
+        window.silentArray = new Float32Array(analyser.frequencyBinCount);
+        silentArray.fill(analyser.minDecibels);
+
+        var intervalToken = setInterval(() => {
+            analyser.getFloatFrequencyData(outputArray);
+        }, 30);
+
+        audio.addEventListener("ended", event => {
+            clearInterval(intervalToken);
+            context.suspend().then(() => {
+                shouldNotBe("outputArray", "silentArray");
+                finishJSTest();
+            });
+        });
+    }
+    window.addEventListener('load', go);
+</script>
+<script src="../../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/security/webaudio-render-remote-audio-blocked-no-crossorigin-expected.txt b/LayoutTests/http/tests/security/webaudio-render-remote-audio-blocked-no-crossorigin-expected.txt
new file mode 100644 (file)
index 0000000..34fac7a
--- /dev/null
@@ -0,0 +1,10 @@
+Ensure that audio is not rendered when tainted by a remote audio resource when CORS is not enabled.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS outputArray is silentArray
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/security/webaudio-render-remote-audio-blocked-no-crossorigin.html b/LayoutTests/http/tests/security/webaudio-render-remote-audio-blocked-no-crossorigin.html
new file mode 100644 (file)
index 0000000..6b6bca4
--- /dev/null
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script src="../../../resources/js-test-pre.js"></script>
+    <script src="../../media-resources/media-file.js"></script>
+</head>
+<body>
+<pre id="console"></pre>
+<script>
+    description("Ensure that audio is not rendered when tainted by a remote audio resource when CORS is not enabled.");
+    window.jsTestIsAsync = true;
+
+    function go() {
+        let audio = new Audio();
+        let mediaFile = findMediaFile("audio", "../../media/resources/1000Hz-sin");
+        let type = mimeTypeForExtension(mediaFile.split('.').pop());
+        audio.src = "http://localhost:8080/security/resources/video-cross-origin-allow.php?name=" + mediaFile + "&type=" + type;
+
+        context = new webkitAudioContext();
+        let mediaSource = context.createMediaElementSource(audio);
+        let analyser = context.createAnalyser();
+        analyser.fftSize = 32;
+
+        mediaSource.connect(analyser);
+
+        context.resume().then(() => {
+            audio.play();
+        });
+
+        window.outputArray = new Float32Array(analyser.frequencyBinCount);
+        window.silentArray = new Float32Array(analyser.frequencyBinCount);
+        silentArray.fill(analyser.minDecibels);
+
+        var intervalToken = setInterval(() => {
+            analyser.getFloatFrequencyData(outputArray);
+        }, 30);
+
+        audio.addEventListener("ended", event => {
+            clearInterval(intervalToken);
+            context.suspend().then(() => {
+                shouldBe("outputArray", "silentArray");
+                finishJSTest();
+            });
+        });
+    }
+    window.addEventListener('load', go);
+</script>
+<script src="../../../resources/js-test-post.js"></script>
+</body>
+</html>
index 0f058b0..79d9f40 100644 (file)
@@ -1,3 +1,21 @@
+2018-05-08  Jer Noble  <jer.noble@apple.com>
+
+        Mute MediaElementSourceNode when tainted.
+        https://bugs.webkit.org/show_bug.cgi?id=184866
+
+        Reviewed by Eric Carlson.
+
+        Test: http/tests/security/webaudio-render-remote-audio-blocked-no-crossorigin.html
+
+        * Modules/webaudio/AudioContext.cpp:
+        (WebCore::AudioContext::wouldTaintOrigin const):
+        * Modules/webaudio/AudioContext.h:
+        * Modules/webaudio/MediaElementAudioSourceNode.cpp:
+        (WebCore::MediaElementAudioSourceNode::setFormat):
+        (WebCore::MediaElementAudioSourceNode::wouldTaintOrigin):
+        (WebCore::MediaElementAudioSourceNode::process):
+        * Modules/webaudio/MediaElementAudioSourceNode.h:
+
 2018-05-08  Eric Carlson  <eric.carlson@apple.com>
 
         Log rtcstats as JSON
index 2c7410d..74ee141 100644 (file)
@@ -387,6 +387,17 @@ void AudioContext::visibilityStateChanged()
     }
 }
 
+bool AudioContext::wouldTaintOrigin(const URL& url) const
+{
+    if (url.protocolIsData())
+        return false;
+
+    if (auto* document = this->document())
+        return !document->securityOrigin().canRequest(url);
+
+    return false;
+}
+
 ExceptionOr<Ref<AudioBuffer>> AudioContext::createBuffer(unsigned numberOfChannels, size_t numberOfFrames, float sampleRate)
 {
     auto audioBuffer = AudioBuffer::create(numberOfChannels, numberOfFrames, sampleRate);
index 2b8a2b1..217d6c8 100644 (file)
@@ -71,6 +71,7 @@ class OscillatorNode;
 class PannerNode;
 class PeriodicWave;
 class ScriptProcessorNode;
+class URL;
 class WaveShaperNode;
 
 // AudioContext is the cornerstone of the web audio API and all AudioNodes are created from it.
@@ -118,6 +119,8 @@ public:
     enum class State { Suspended, Running, Interrupted, Closed };
     State state() const;
 
+    bool wouldTaintOrigin(const URL&) const;
+
     // The AudioNode create methods are called on the main thread (from JavaScript).
     Ref<AudioBufferSourceNode> createBufferSource();
 #if ENABLE(VIDEO)
index 77c7fbc..9eca86f 100644 (file)
@@ -67,6 +67,8 @@ MediaElementAudioSourceNode::~MediaElementAudioSourceNode()
 
 void MediaElementAudioSourceNode::setFormat(size_t numberOfChannels, float sourceSampleRate)
 {
+    m_muted = wouldTaintOrigin();
+
     if (numberOfChannels != m_sourceNumberOfChannels || sourceSampleRate != m_sourceSampleRate) {
         if (!numberOfChannels || numberOfChannels > AudioContext::maxNumberOfChannels() || sourceSampleRate < minSampleRate || sourceSampleRate > maxSampleRate) {
             // process() will generate silence for these uninitialized values.
@@ -100,11 +102,22 @@ void MediaElementAudioSourceNode::setFormat(size_t numberOfChannels, float sourc
     }
 }
 
+bool MediaElementAudioSourceNode::wouldTaintOrigin()
+{
+    if (!m_mediaElement->hasSingleSecurityOrigin())
+        return true;
+
+    if (m_mediaElement->didPassCORSAccessCheck())
+        return false;
+
+    return context().wouldTaintOrigin(m_mediaElement->currentSrc());
+}
+
 void MediaElementAudioSourceNode::process(size_t numberOfFrames)
 {
     AudioBus* outputBus = output(0)->bus();
 
-    if (!m_sourceNumberOfChannels || !m_sourceSampleRate) {
+    if (m_muted || !m_sourceNumberOfChannels || !m_sourceSampleRate) {
         outputBus->zero();
         return;
     }
index 0cafdc9..bb9f714 100644 (file)
@@ -51,7 +51,7 @@ public:
     
     // AudioSourceProviderClient
     void setFormat(size_t numberOfChannels, float sampleRate) override;
-    
+
     void lock();
     void unlock();
 
@@ -64,11 +64,14 @@ private:
     // As an audio source, we will never propagate silence.
     bool propagatesSilence() const override { return false; }
 
+    bool wouldTaintOrigin();
+
     Ref<HTMLMediaElement> m_mediaElement;
     Lock m_processMutex;
 
     unsigned m_sourceNumberOfChannels;
     double m_sourceSampleRate;
+    bool m_muted { false };
 
     std::unique_ptr<MultiChannelResampler> m_multiChannelResampler;
 };