Changeset 237770 in webkit


Ignore:
Timestamp:
Nov 3, 2018 6:30:30 AM (5 years ago)
Author:
eric.carlson@apple.com
Message:

[MediaStream] enumerateDevices should not expose devices that are not available to getUserMedia
https://bugs.webkit.org/show_bug.cgi?id=191177
<rdar://problem/45747873>

Reviewed by Jer Noble.

Source/WebCore:

Test: http/tests/media/media-stream/enumerate-devices-iframe-allow-attribute.html

  • Modules/mediastream/MediaDevicesRequest.cpp:

(WebCore::MediaDevicesRequest::start): Only expose devices that are available to gUM.

  • Modules/mediastream/UserMediaController.cpp:

(WebCore::isSecure): Moved from UserMediaRequest.cpp.
(WebCore::isAllowedToUse): Ditto.
(WebCore::UserMediaController::canCallGetUserMedia): Modified from UserMediaRequest.cpp.
(WebCore::UserMediaController::logGetUserMediaDenial): Log reason for denial.

  • Modules/mediastream/UserMediaController.h:
  • Modules/mediastream/UserMediaRequest.cpp:

(WebCore::UserMediaRequest::start): Use UserMediaController::canCallGetUserMedia.
(WebCore::isSecure): Deleted.
(WebCore::isAllowedToUse): Deleted.
(WebCore::canCallGetUserMedia): Deleted.

LayoutTests:

  • http/tests/media/media-stream/enumerate-devices-iframe-allow-attribute-expected.txt: Added.
  • http/tests/media/media-stream/enumerate-devices-iframe-allow-attribute.html: Added.
  • http/tests/media/media-stream/resources/enumerate-devices-iframe.html: Added.
  • http/tests/ssl/media-stream/get-user-media-different-host-expected.txt: Rebased for updated logging.
  • http/tests/ssl/media-stream/get-user-media-nested-expected.txt: Ditto.
Location:
trunk
Files:
3 added
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r237766 r237770  
     12018-11-03  Eric Carlson  <eric.carlson@apple.com>
     2
     3        [MediaStream] enumerateDevices should not expose devices that are not available to getUserMedia
     4        https://bugs.webkit.org/show_bug.cgi?id=191177
     5        <rdar://problem/45747873>
     6
     7        Reviewed by Jer Noble.
     8
     9        * http/tests/media/media-stream/enumerate-devices-iframe-allow-attribute-expected.txt: Added.
     10        * http/tests/media/media-stream/enumerate-devices-iframe-allow-attribute.html: Added.
     11        * http/tests/media/media-stream/resources/enumerate-devices-iframe.html: Added.
     12        * http/tests/ssl/media-stream/get-user-media-different-host-expected.txt: Rebased for updated logging.
     13        * http/tests/ssl/media-stream/get-user-media-nested-expected.txt: Ditto.
     14
    1152018-11-02  Justin Michaud  <justin_michaud@apple.com>
    216
  • trunk/LayoutTests/http/tests/ssl/media-stream/get-user-media-different-host-expected.txt

    r231450 r237770  
    1 CONSOLE MESSAGE: line 52: The top-level frame has prevented a document with a different security origin to call getUserMedia.
     1CONSOLE MESSAGE: line 52: The top-level frame has prevented a document with a different security origin from calling getUserMedia.
    22Tests that getUserMedia fails when the top level document and iframe do not have the same domain.
    33
  • trunk/LayoutTests/http/tests/ssl/media-stream/get-user-media-nested-expected.txt

    r231450 r237770  
    1 CONSOLE MESSAGE: line 52: The top-level frame has prevented a document with a different security origin to call getUserMedia.
     1CONSOLE MESSAGE: line 52: The top-level frame has prevented a document with a different security origin from calling getUserMedia.
    22Tests that getUserMedia fails when the top level document and iframe do not have the same domain.
    33
  • trunk/LayoutTests/imported/w3c/web-platform-tests/mediacapture-streams/MediaStream-default-feature-policy.https-expected.txt

    r235484 r237770  
    1 CONSOLE MESSAGE: line 16: The top-level frame has prevented a document with a different security origin to call getUserMedia.
    2 CONSOLE MESSAGE: line 16: The top-level frame has prevented a document with a different security origin to call getUserMedia.
    3 CONSOLE MESSAGE: line 16: The top-level frame has prevented a document with a different security origin to call getUserMedia.
    4 CONSOLE MESSAGE: line 16: The top-level frame has prevented a document with a different security origin to call getUserMedia.
    5 CONSOLE MESSAGE: line 16: The top-level frame has prevented a document with a different security origin to call getUserMedia.
    6 CONSOLE MESSAGE: line 16: The top-level frame has prevented a document with a different security origin to call getUserMedia.
    7 CONSOLE MESSAGE: line 16: The top-level frame has prevented a document with a different security origin to call getUserMedia.
     1CONSOLE MESSAGE: line 16: The top-level frame has prevented a document with a different security origin from calling getUserMedia.
     2CONSOLE MESSAGE: line 16: The top-level frame has prevented a document with a different security origin from calling getUserMedia.
     3CONSOLE MESSAGE: line 16: The top-level frame has prevented a document with a different security origin from calling getUserMedia.
     4CONSOLE MESSAGE: line 16: The top-level frame has prevented a document with a different security origin from calling getUserMedia.
     5CONSOLE MESSAGE: line 16: The top-level frame has prevented a document with a different security origin from calling getUserMedia.
     6CONSOLE MESSAGE: line 16: The top-level frame has prevented a document with a different security origin from calling getUserMedia.
     7CONSOLE MESSAGE: line 16: The top-level frame has prevented a document with a different security origin from calling getUserMedia.
    88
    99
  • trunk/Source/WebCore/ChangeLog

    r237766 r237770  
     12018-11-03  Eric Carlson  <eric.carlson@apple.com>
     2
     3        [MediaStream] enumerateDevices should not expose devices that are not available to getUserMedia
     4        https://bugs.webkit.org/show_bug.cgi?id=191177
     5        <rdar://problem/45747873>
     6
     7        Reviewed by Jer Noble.
     8
     9        Test: http/tests/media/media-stream/enumerate-devices-iframe-allow-attribute.html
     10
     11        * Modules/mediastream/MediaDevicesRequest.cpp:
     12        (WebCore::MediaDevicesRequest::start): Only expose devices that are available to gUM.
     13
     14        * Modules/mediastream/UserMediaController.cpp:
     15        (WebCore::isSecure): Moved from UserMediaRequest.cpp.
     16        (WebCore::isAllowedToUse): Ditto.
     17        (WebCore::UserMediaController::canCallGetUserMedia): Modified from UserMediaRequest.cpp.
     18        (WebCore::UserMediaController::logGetUserMediaDenial): Log reason for denial.
     19        * Modules/mediastream/UserMediaController.h:
     20
     21        * Modules/mediastream/UserMediaRequest.cpp:
     22        (WebCore::UserMediaRequest::start): Use UserMediaController::canCallGetUserMedia.
     23        (WebCore::isSecure): Deleted.
     24        (WebCore::isAllowedToUse): Deleted.
     25        (WebCore::canCallGetUserMedia): Deleted.
     26
    1272018-11-02  Justin Michaud  <justin_michaud@apple.com>
    228
  • trunk/Source/WebCore/Modules/mediastream/MediaDevicesRequest.cpp

    r237643 r237770  
    104104void MediaDevicesRequest::start()
    105105{
     106    auto& document = downcast<Document>(*scriptExecutionContext());
     107    auto* controller = UserMediaController::from(document.page());
     108    if (!controller) {
     109        callOnMainThread([protectedThis = makeRef(*this)]() {
     110            protectedThis->m_promise.resolve({ });
     111        });
     112
     113        return;
     114    }
     115
     116    auto microphoneAccess = controller->canCallGetUserMedia(document, true, false);
     117    auto cameraAccess = controller->canCallGetUserMedia(document, false, true);
     118    bool canAccessMicrophone = microphoneAccess == UserMediaController::GetUserMediaAccess::CanCall;
     119    bool canAccessCamera = cameraAccess == UserMediaController::GetUserMediaAccess::CanCall;
     120    if (!canAccessMicrophone && !canAccessCamera) {
     121        controller->logGetUserMediaDenial(document, !canAccessMicrophone ? microphoneAccess : cameraAccess, UserMediaController::BlockedCaller::EnumerateDevices);
     122        callOnMainThread([protectedThis = makeRef(*this)]() {
     123            protectedThis->m_promise.resolve({ });
     124        });
     125
     126        return;
     127    }
     128
    106129    // This lambda keeps |this| alive until the request completes or is canceled.
    107     auto completion = [this, protectedThis = makeRef(*this)] (const Vector<CaptureDevice>& captureDevices, const String& deviceIdentifierHashSalt, bool originHasPersistentAccess) mutable {
     130    auto completion = [this, protectedThis = makeRef(*this), canAccessMicrophone, canAccessCamera] (const Vector<CaptureDevice>& captureDevices, const String& deviceIdentifierHashSalt, bool originHasPersistentAccess) mutable {
    108131
    109132        m_enumerationRequest = nullptr;
     
    118141        bool revealIdsAndLabels = originHasPersistentAccess || document.hasHadCaptureMediaStreamTrack();
    119142        for (auto& deviceInfo : captureDevices) {
     143            if (!canAccessMicrophone && deviceInfo.type() == CaptureDevice::DeviceType::Microphone)
     144                continue;
     145            if (!canAccessCamera && deviceInfo.type() == CaptureDevice::DeviceType::Camera)
     146                continue;
     147
    120148            auto label = emptyString();
    121149            auto id = emptyString();
     
    141169    };
    142170
    143     m_enumerationRequest = MediaDevicesEnumerationRequest::create(*downcast<Document>(scriptExecutionContext()), WTFMove(completion));
     171    m_enumerationRequest = MediaDevicesEnumerationRequest::create(document, WTFMove(completion));
    144172    m_enumerationRequest->start();
    145173}
  • trunk/Source/WebCore/Modules/mediastream/UserMediaController.cpp

    r218601 r237770  
    11/*
    22 * Copyright (C) 2012 Google Inc. All rights reserved.
     3 * Copyright (C) 2013-2018 Apple Inc. All rights reserved.
    34 *
    45 * Redistribution and use in source and binary forms, with or without
     
    2829#if ENABLE(MEDIA_STREAM)
    2930
     31#include "DOMWindow.h"
     32#include "DeprecatedGlobalSettings.h"
     33#include "Document.h"
     34#include "DocumentLoader.h"
     35#include "Frame.h"
     36#include "HTMLIFrameElement.h"
     37#include "HTMLParserIdioms.h"
     38#include "SchemeRegistry.h"
    3039#include "UserMediaRequest.h"
    3140
     
    5261}
    5362
     63static bool isSecure(DocumentLoader& documentLoader)
     64{
     65    auto& response = documentLoader.response();
     66    if (SecurityOrigin::isLocalHostOrLoopbackIPAddress(documentLoader.response().url().host()))
     67        return true;
     68    return SchemeRegistry::shouldTreatURLSchemeAsSecure(response.url().protocol().toStringWithoutCopying())
     69        && response.certificateInfo()
     70        && !response.certificateInfo()->containsNonRootSHA1SignedCertificate();
     71}
     72
     73static bool isAllowedToUse(Document& document, Document& topDocument, bool requiresAudio, bool requiresVideo)
     74{
     75    if (&document == &topDocument)
     76        return true;
     77
     78    auto* parentDocument = document.parentDocument();
     79    if (!parentDocument)
     80        return false;
     81
     82    if (document.securityOrigin().isSameSchemeHostPort(parentDocument->securityOrigin()))
     83        return true;
     84
     85    auto* element = document.ownerElement();
     86    ASSERT(element);
     87    if (!element)
     88        return false;
     89
     90    if (!is<HTMLIFrameElement>(*element))
     91        return false;
     92    auto& allow = downcast<HTMLIFrameElement>(*element).allow();
     93
     94    bool allowCameraAccess = false;
     95    bool allowMicrophoneAccess = false;
     96    for (auto allowItem : StringView { allow }.split(';')) {
     97        auto item = allowItem.stripLeadingAndTrailingMatchedCharacters(isHTMLSpace<UChar>);
     98        if (!allowCameraAccess && item == "camera")
     99            allowCameraAccess = true;
     100        else if (!allowMicrophoneAccess && item == "microphone")
     101            allowMicrophoneAccess = true;
     102    }
     103    return (allowCameraAccess || !requiresVideo) && (allowMicrophoneAccess || !requiresAudio);
     104}
     105
     106UserMediaController::GetUserMediaAccess UserMediaController::canCallGetUserMedia(Document& document, bool wantsAudio, bool wantsVideo)
     107{
     108    ASSERT(wantsAudio || wantsVideo);
     109
     110    bool requiresSecureConnection = DeprecatedGlobalSettings::mediaCaptureRequiresSecureConnection();
     111    auto& documentLoader = *document.loader();
     112    if (requiresSecureConnection && !isSecure(documentLoader))
     113        return GetUserMediaAccess::InsecureDocument;
     114
     115    auto& topDocument = document.topDocument();
     116    if (&document != &topDocument) {
     117        for (auto* ancestorDocument = &document; ancestorDocument != &topDocument; ancestorDocument = ancestorDocument->parentDocument()) {
     118            if (requiresSecureConnection && !isSecure(*ancestorDocument->loader()))
     119                return GetUserMediaAccess::InsecureParent;
     120
     121            if (!isAllowedToUse(*ancestorDocument, topDocument, wantsAudio, wantsVideo))
     122                return GetUserMediaAccess::BlockedByParent;
     123        }
     124    }
     125
     126    return GetUserMediaAccess::CanCall;
     127}
     128
     129void UserMediaController::logGetUserMediaDenial(Document& document, GetUserMediaAccess access, BlockedCaller caller)
     130{
     131    auto& domWindow = *document.domWindow();
     132    const char* callerName = caller == BlockedCaller::GetUserMedia ? "getUserMedia" : "enumerateDevices";
     133    switch (access) {
     134    case UserMediaController::GetUserMediaAccess::InsecureDocument:
     135        domWindow.printErrorMessage(makeString("Trying to call ", callerName, " from an insecure document."));
     136        break;
     137    case UserMediaController::GetUserMediaAccess::InsecureParent:
     138        domWindow.printErrorMessage(makeString("Trying to call ", callerName, " from a document with an insecure parent frame."));
     139        break;
     140    case UserMediaController::GetUserMediaAccess::BlockedByParent:
     141        domWindow.printErrorMessage(makeString("The top-level frame has prevented a document with a different security origin from calling ", callerName, "."));
     142        break;
     143    case UserMediaController::GetUserMediaAccess::CanCall:
     144        break;
     145    }
     146}
     147
    54148} // namespace WebCore
    55149
  • trunk/Source/WebCore/Modules/mediastream/UserMediaController.h

    r235086 r237770  
    5252    void removeDeviceChangeObserver(UserMediaClient::DeviceChangeObserverToken);
    5353
     54    enum class GetUserMediaAccess {
     55        CanCall,
     56        InsecureDocument,
     57        InsecureParent,
     58        BlockedByParent
     59    };
     60    GetUserMediaAccess canCallGetUserMedia(Document&, bool wantsAudio, bool wantsVideo);
     61
     62    enum class BlockedCaller {
     63        GetUserMedia,
     64        EnumerateDevices
     65    };
     66    void logGetUserMediaDenial(Document&, GetUserMediaAccess, BlockedCaller);
     67
    5468    WEBCORE_EXPORT static const char* supplementName();
    5569    static UserMediaController* from(Page* page) { return static_cast<UserMediaController*>(Supplement<Page>::from(page, supplementName())); }
  • trunk/Source/WebCore/Modules/mediastream/UserMediaRequest.cpp

    r236877 r237770  
    22 * Copyright (C) 2011 Ericsson AB. All rights reserved.
    33 * Copyright (C) 2012 Google Inc. All rights reserved.
    4  * Copyright (C) 2013-2017 Apple Inc. All rights reserved.
     4 * Copyright (C) 2013-2018 Apple Inc. All rights reserved.
    55 * Copyright (C) 2013 Nokia Corporation and/or its subsidiary(-ies).
    66 *
     
    3737#if ENABLE(MEDIA_STREAM)
    3838
    39 #include "CaptureDeviceManager.h"
    40 #include "DeprecatedGlobalSettings.h"
    4139#include "Document.h"
    42 #include "DocumentLoader.h"
    4340#include "Frame.h"
    44 #include "HTMLIFrameElement.h"
    45 #include "HTMLParserIdioms.h"
    4641#include "JSMediaStream.h"
    4742#include "JSOverconstrainedError.h"
     
    8378        return nullptr;
    8479    return &m_scriptExecutionContext->topOrigin();
    85 }
    86 
    87 static bool isSecure(DocumentLoader& documentLoader)
    88 {
    89     auto& response = documentLoader.response();
    90     if (SecurityOrigin::isLocalHostOrLoopbackIPAddress(documentLoader.response().url().host()))
    91         return true;
    92     return SchemeRegistry::shouldTreatURLSchemeAsSecure(response.url().protocol().toStringWithoutCopying())
    93         && response.certificateInfo()
    94         && !response.certificateInfo()->containsNonRootSHA1SignedCertificate();
    95 }
    96 
    97 static bool isAllowedToUse(Document& document, Document& topDocument, bool requiresAudio, bool requiresVideo)
    98 {
    99     if (&document == &topDocument)
    100         return true;
    101 
    102     auto* parentDocument = document.parentDocument();
    103     if (!parentDocument)
    104         return false;
    105 
    106     if (document.securityOrigin().isSameSchemeHostPort(parentDocument->securityOrigin()))
    107         return true;
    108 
    109     auto* element = document.ownerElement();
    110     ASSERT(element);
    111     if (!element)
    112         return false;
    113 
    114     if (!is<HTMLIFrameElement>(*element))
    115         return false;
    116     auto& allow = downcast<HTMLIFrameElement>(*element).allow();
    117 
    118     bool allowCameraAccess = false;
    119     bool allowMicrophoneAccess = false;
    120     for (auto allowItem : StringView { allow }.split(';')) {
    121         auto item = allowItem.stripLeadingAndTrailingMatchedCharacters(isHTMLSpace<UChar>);
    122         if (!allowCameraAccess && item == "camera")
    123             allowCameraAccess = true;
    124         else if (!allowMicrophoneAccess && item == "microphone")
    125             allowMicrophoneAccess = true;
    126     }
    127     return (allowCameraAccess || !requiresVideo) && (allowMicrophoneAccess || !requiresAudio);
    128 }
    129 
    130 static bool canCallGetUserMedia(Document& document, bool wantsAudio, bool wantsVideo, String& errorMessage)
    131 {
    132     ASSERT(wantsAudio || wantsVideo);
    133 
    134     bool requiresSecureConnection = DeprecatedGlobalSettings::mediaCaptureRequiresSecureConnection();
    135     auto& documentLoader = *document.loader();
    136     if (requiresSecureConnection && !isSecure(documentLoader)) {
    137         errorMessage = "Trying to call getUserMedia from an insecure document.";
    138         return false;
    139     }
    140 
    141     auto& topDocument = document.topDocument();
    142     if (&document != &topDocument) {
    143         for (auto* ancestorDocument = &document; ancestorDocument != &topDocument; ancestorDocument = ancestorDocument->parentDocument()) {
    144             if (requiresSecureConnection && !isSecure(*ancestorDocument->loader())) {
    145                 errorMessage = "Trying to call getUserMedia from a document with an insecure parent frame.";
    146                 return false;
    147             }
    148 
    149             if (!isAllowedToUse(*ancestorDocument, topDocument, wantsAudio, wantsVideo)) {
    150                 errorMessage = "The top-level frame has prevented a document with a different security origin to call getUserMedia.";
    151                 return false;
    152             }
    153         }
    154     }
    155    
    156     return true;
    15780}
    15881
     
    267190    // 6.10 Permission Failure: Reject p with a new DOMException object whose name attribute has
    268191    //      the value NotAllowedError.
    269     String errorMessage;
    270     if (!canCallGetUserMedia(document, m_request.audioConstraints.isValid, m_request.videoConstraints.isValid, errorMessage)) {
     192    auto access = controller->canCallGetUserMedia(document, m_request.audioConstraints.isValid, m_request.videoConstraints.isValid);
     193    if (access != UserMediaController::GetUserMediaAccess::CanCall) {
    271194        deny(MediaAccessDenialReason::PermissionDenied);
    272         document.domWindow()->printErrorMessage(errorMessage);
     195        controller->logGetUserMediaDenial(document, access, UserMediaController::BlockedCaller::GetUserMedia);
    273196        return;
    274197    }
Note: See TracChangeset for help on using the changeset viewer.