Changeset 282366 in webkit


Ignore:
Timestamp:
Sep 13, 2021 3:18:26 PM (10 months ago)
Author:
Chris Dumez
Message:

Relax BroadcastChannel origin partitioning if iframe has storage access
https://bugs.webkit.org/show_bug.cgi?id=230164

Reviewed by Alex Christensen.

Source/WebCore:

In r282105, we started partitioning origins for BroadcastChannel using topOrigin/frameOrigin.
This prevents a topFrame of origin A to message a subframe of origin A if that subframe if under
another domain B. However, the document.requestStorageAccess() API exists to relax storage
partitioning and Gecko relies on this to relax BroadcastChannel origin partitioning as well.
This patch aligns WebKit's behavior with Gecko.

Test: http/tests/messaging/broadcastchannel-partitioning-with-storage-access.html

  • dom/BroadcastChannel.cpp:

(WebCore::shouldPartitionOrigin):
(WebCore::BroadcastChannel::MainThreadBridge::create):
(WebCore::BroadcastChannel::MainThreadBridge::name const):
(WebCore::BroadcastChannel::MainThreadBridge::identifier const):
(WebCore::BroadcastChannel::MainThreadBridge::MainThreadBridge):
(WebCore::BroadcastChannel::MainThreadBridge::ensureOnMainThread):
(WebCore::BroadcastChannel::MainThreadBridge::registerChannel):
(WebCore::BroadcastChannel::MainThreadBridge::unregisterChannel):
(WebCore::BroadcastChannel::MainThreadBridge::postMessage):
(WebCore::BroadcastChannel::BroadcastChannel):
(WebCore::BroadcastChannel::~BroadcastChannel):
(WebCore::BroadcastChannel::identifier const):
(WebCore::BroadcastChannel::name const):
(WebCore::BroadcastChannel::postMessage):
(WebCore::BroadcastChannel::close):
(WebCore::BroadcastChannel::dispatchMessage):

  • dom/BroadcastChannel.h:
  • page/ChromeClient.h:

(WebCore::ChromeClient::hasPageLevelStorageAccess const):

Source/WebKit:

  • WebProcess/WebCoreSupport/WebChromeClient.cpp:

(WebKit::WebChromeClient::hasPageLevelStorageAccess const):

  • WebProcess/WebCoreSupport/WebChromeClient.h:

LayoutTests:

Add layout test coverage.

  • http/tests/messaging/broadcastchannel-partitioning-with-storage-access-expected.txt: Added.
  • http/tests/messaging/broadcastchannel-partitioning-with-storage-access.html: Added.
  • http/tests/messaging/resources/broadcastchannel-partitioning-with-storage-access-iframe.html: Added.
  • http/tests/messaging/resources/broadcastchannel-partitioning-with-storage-access-popup.html: Added.
Location:
trunk
Files:
4 added
10 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r282363 r282366  
     12021-09-13  Chris Dumez  <cdumez@apple.com>
     2
     3        Relax BroadcastChannel origin partitioning if iframe has storage access
     4        https://bugs.webkit.org/show_bug.cgi?id=230164
     5
     6        Reviewed by Alex Christensen.
     7
     8        Add layout test coverage.
     9
     10        * http/tests/messaging/broadcastchannel-partitioning-with-storage-access-expected.txt: Added.
     11        * http/tests/messaging/broadcastchannel-partitioning-with-storage-access.html: Added.
     12        * http/tests/messaging/resources/broadcastchannel-partitioning-with-storage-access-iframe.html: Added.
     13        * http/tests/messaging/resources/broadcastchannel-partitioning-with-storage-access-popup.html: Added.
     14
    1152021-09-13  Arcady Goldmints-Orlov  <agoldmints@igalia.com>
    216
  • trunk/LayoutTests/platform/mac-wk1/TestExpectations

    r282358 r282366  
    7979editing/async-clipboard/clipboard-read-text-same-origin.html [ Skip ]
    8080editing/async-clipboard/sanitize-when-reading-markup.html [ Skip ]
     81
     82# No requestStorageAccess() support for WK1.
     83http/tests/messaging/broadcastchannel-partitioning-with-storage-access.html [ Skip ]
    8184
    8285imported/w3c/web-platform-tests/websockets/Close-1000-reason.any.html [ Pass Failure ]
  • trunk/LayoutTests/platform/win/TestExpectations

    r282357 r282366  
    46374637fast/html/broadcast-channel-between-different-sessions.html [ Skip ]
    46384638http/tests/messaging/broadcastchannel-partitioning.html [ Skip ]
     4639http/tests/messaging/broadcastchannel-partitioning-with-storage-access.html [ Skip ]
    46394640
    46404641# This test is skipped because the necessary feature flag functionality specific to the Windows WebKit legacy port is
  • trunk/Source/WebCore/ChangeLog

    r282362 r282366  
     12021-09-13  Chris Dumez  <cdumez@apple.com>
     2
     3        Relax BroadcastChannel origin partitioning if iframe has storage access
     4        https://bugs.webkit.org/show_bug.cgi?id=230164
     5
     6        Reviewed by Alex Christensen.
     7
     8        In r282105, we started partitioning origins for BroadcastChannel using topOrigin/frameOrigin.
     9        This prevents a topFrame of origin A to message a subframe of origin A if that subframe if under
     10        another domain B. However, the `document.requestStorageAccess()` API exists to relax storage
     11        partitioning and Gecko relies on this to relax BroadcastChannel origin partitioning as well.
     12        This patch aligns WebKit's behavior with Gecko.
     13
     14        Test: http/tests/messaging/broadcastchannel-partitioning-with-storage-access.html
     15
     16        * dom/BroadcastChannel.cpp:
     17        (WebCore::shouldPartitionOrigin):
     18        (WebCore::BroadcastChannel::MainThreadBridge::create):
     19        (WebCore::BroadcastChannel::MainThreadBridge::name const):
     20        (WebCore::BroadcastChannel::MainThreadBridge::identifier const):
     21        (WebCore::BroadcastChannel::MainThreadBridge::MainThreadBridge):
     22        (WebCore::BroadcastChannel::MainThreadBridge::ensureOnMainThread):
     23        (WebCore::BroadcastChannel::MainThreadBridge::registerChannel):
     24        (WebCore::BroadcastChannel::MainThreadBridge::unregisterChannel):
     25        (WebCore::BroadcastChannel::MainThreadBridge::postMessage):
     26        (WebCore::BroadcastChannel::BroadcastChannel):
     27        (WebCore::BroadcastChannel::~BroadcastChannel):
     28        (WebCore::BroadcastChannel::identifier const):
     29        (WebCore::BroadcastChannel::name const):
     30        (WebCore::BroadcastChannel::postMessage):
     31        (WebCore::BroadcastChannel::close):
     32        (WebCore::BroadcastChannel::dispatchMessage):
     33        * dom/BroadcastChannel.h:
     34        * page/ChromeClient.h:
     35        (WebCore::ChromeClient::hasPageLevelStorageAccess const):
     36
    1372021-09-13  Youenn Fablet  <youenn@apple.com>
    238
  • trunk/Source/WebCore/dom/BroadcastChannel.cpp

    r282105 r282366  
    2828
    2929#include "BroadcastChannelRegistry.h"
     30#include "Chrome.h"
     31#include "ChromeClient.h"
    3032#include "EventNames.h"
    3133#include "MessageEvent.h"
     
    5860}
    5961
     62static bool shouldPartitionOrigin(Document& document)
     63{
     64    if (!document.settings().broadcastChannelOriginPartitioningEnabled())
     65        return false;
     66
     67#if ENABLE(RESOURCE_LOAD_STATISTICS)
     68    if (!document.settings().storageAccessAPIEnabled())
     69        return true;
     70
     71    auto* page = document.page();
     72    if (!page)
     73        return true;
     74
     75    return !page->chrome().client().hasPageLevelStorageAccess(RegistrableDomain::uncheckedCreateFromHost(document.topDocument().securityOrigin().host()), RegistrableDomain::uncheckedCreateFromHost(document.securityOrigin().host()));
     76#else
     77    return true;
     78#endif
     79}
     80
     81class BroadcastChannel::MainThreadBridge : public ThreadSafeRefCounted<MainThreadBridge, WTF::DestructionThread::Main> {
     82public:
     83    static Ref<MainThreadBridge> create(BroadcastChannel& channel, const String& name)
     84    {
     85        return adoptRef(*new MainThreadBridge(channel, name));
     86    }
     87
     88    void registerChannel();
     89    void unregisterChannel();
     90    void postMessage(Ref<SerializedScriptValue>&&);
     91
     92    String name() const { return m_name.isolatedCopy(); }
     93    BroadcastChannelIdentifier identifier() const { return m_identifier; }
     94
     95private:
     96    MainThreadBridge(BroadcastChannel&, const String& name);
     97
     98    void ensureOnMainThread(Function<void(Document&)>&&);
     99
     100    WeakPtr<BroadcastChannel> m_broadcastChannel;
     101    const BroadcastChannelIdentifier m_identifier;
     102    const String m_name; // Main thread only.
     103    ClientOrigin m_origin; // Main thread only.
     104};
     105
     106BroadcastChannel::MainThreadBridge::MainThreadBridge(BroadcastChannel& channel, const String& name)
     107    : m_broadcastChannel(makeWeakPtr(channel))
     108    , m_identifier(BroadcastChannelIdentifier::generateThreadSafe())
     109    , m_name(name.isolatedCopy())
     110{
     111}
     112
     113void BroadcastChannel::MainThreadBridge::ensureOnMainThread(Function<void(Document&)>&& task)
     114{
     115    ASSERT(m_broadcastChannel);
     116    if (!m_broadcastChannel)
     117        return;
     118
     119    auto* context = m_broadcastChannel->scriptExecutionContext();
     120    if (!context)
     121        return;
     122    ASSERT(context->isContextThread());
     123
     124    Ref protectedThis { *this };
     125    if (is<Document>(*context))
     126        task(downcast<Document>(*context));
     127    else {
     128        downcast<WorkerGlobalScope>(*context).thread().workerLoaderProxy().postTaskToLoader([protectedThis = WTFMove(protectedThis), task = WTFMove(task)](auto& context) {
     129            task(downcast<Document>(context));
     130        });
     131    }
     132}
     133
     134void BroadcastChannel::MainThreadBridge::registerChannel()
     135{
     136    ensureOnMainThread([this, contextIdentifier = m_broadcastChannel->scriptExecutionContext()->contextIdentifier()](auto& document) {
     137        m_origin = { shouldPartitionOrigin(document) ? document.topOrigin().data() : document.securityOrigin().data(), document.securityOrigin().data() };
     138        if (auto* page = document.page())
     139            page->broadcastChannelRegistry().registerChannel(m_origin, m_name, m_identifier);
     140        channelToContextIdentifier().add(m_identifier, contextIdentifier);
     141    });
     142}
     143
     144void BroadcastChannel::MainThreadBridge::unregisterChannel()
     145{
     146    ensureOnMainThread([this](auto& document) {
     147        if (auto* page = document.page())
     148            page->broadcastChannelRegistry().unregisterChannel(m_origin, m_name, m_identifier);
     149        channelToContextIdentifier().remove(m_identifier);
     150    });
     151}
     152
     153void BroadcastChannel::MainThreadBridge::postMessage(Ref<SerializedScriptValue>&& message)
     154{
     155    ensureOnMainThread([this, message = WTFMove(message)](auto& document) mutable {
     156        auto* page = document.page();
     157        if (!page)
     158            return;
     159
     160        auto blobHandles = message->blobHandles();
     161        page->broadcastChannelRegistry().postMessage(m_origin, m_name, m_identifier, WTFMove(message), [blobHandles = WTFMove(blobHandles)] {
     162            // Keeps Blob data inside messageData alive until the message has been delivered.
     163        });
     164    });
     165}
     166
    60167BroadcastChannel::BroadcastChannel(ScriptExecutionContext& context, const String& name)
    61168    : ActiveDOMObject(&context)
    62     , m_name(name)
    63     , m_origin { context.settingsValues().broadcastChannelOriginPartitioningEnabled ? context.topOrigin().data() : context.securityOrigin()->data(), context.securityOrigin()->data() }
    64     , m_identifier(BroadcastChannelIdentifier::generateThreadSafe())
     169    , m_mainThreadBridge(MainThreadBridge::create(*this, name))
    65170{
    66171    {
    67172        Locker locker { allBroadcastChannelsLock };
    68         allBroadcastChannels().add(m_identifier, this);
    69     }
    70 
    71     ensureOnMainThread([origin = crossThreadCopy(m_origin), name = crossThreadCopy(m_name), contextIdentifier = context.contextIdentifier(), channelIdentifier = m_identifier](auto& document) {
    72         if (auto* page = document.page())
    73             page->broadcastChannelRegistry().registerChannel(origin, name, channelIdentifier);
    74         channelToContextIdentifier().add(channelIdentifier, contextIdentifier);
    75     });
     173        allBroadcastChannels().add(m_mainThreadBridge->identifier(), this);
     174    }
     175    m_mainThreadBridge->registerChannel();
    76176}
    77177
     
    81181    {
    82182        Locker locker { allBroadcastChannelsLock };
    83         allBroadcastChannels().remove(m_identifier);
    84     }
     183        allBroadcastChannels().remove(m_mainThreadBridge->identifier());
     184    }
     185}
     186
     187BroadcastChannelIdentifier BroadcastChannel::identifier() const
     188{
     189    return m_mainThreadBridge->identifier();
     190}
     191
     192String BroadcastChannel::name() const
     193{
     194    return m_mainThreadBridge->name();
    85195}
    86196
     
    96206    ASSERT(ports.isEmpty());
    97207
    98     ensureOnMainThread([origin = crossThreadCopy(m_origin), name = crossThreadCopy(m_name), identifier = m_identifier, messageData = messageData.releaseReturnValue()](auto& document) mutable {
    99         auto* page = document.page();
    100         if (!page)
    101             return;
    102 
    103         auto blobHandles = messageData->blobHandles();
    104         page->broadcastChannelRegistry().postMessage(origin, name, identifier, WTFMove(messageData), [blobHandles = WTFMove(blobHandles)] {
    105             // Keeps Blob data inside messageData alive until the message has been delivered.
    106         });
    107     });
    108 
     208    m_mainThreadBridge->postMessage(messageData.releaseReturnValue());
    109209    return { };
    110210}
     
    116216
    117217    m_isClosed = true;
    118     ensureOnMainThread([origin = crossThreadCopy(m_origin), name = crossThreadCopy(m_name), channelIdentifier = m_identifier](auto& document) {
    119         if (auto* page = document.page())
    120             page->broadcastChannelRegistry().unregisterChannel(origin, name, channelIdentifier);
    121         channelToContextIdentifier().remove(channelIdentifier);
    122     });
     218    m_mainThreadBridge->unregisterChannel();
    123219}
    124220
     
    150246
    151247    queueTaskKeepingObjectAlive(*this, TaskSource::PostedMessageQueue, [this, message = WTFMove(message)]() mutable {
    152         if (!m_isClosed)
    153             dispatchEvent(MessageEvent::create({ }, WTFMove(message), m_origin.clientOrigin.toString()));
    154     });
    155 }
    156 
    157 void BroadcastChannel::ensureOnMainThread(Function<void(Document&)>&& task)
    158 {
    159     auto* context = scriptExecutionContext();
    160     if (!context)
    161         return;
    162 
    163     if (is<Document>(*context))
    164         task(downcast<Document>(*context));
    165     else {
    166         downcast<WorkerGlobalScope>(*context).thread().workerLoaderProxy().postTaskToLoader([task = WTFMove(task)](auto& context) {
    167             task(downcast<Document>(context));
    168         });
    169     }
     248        if (!m_isClosed && scriptExecutionContext())
     249            dispatchEvent(MessageEvent::create({ }, WTFMove(message), scriptExecutionContext()->securityOrigin()->toString()));
     250    });
    170251}
    171252
  • trunk/Source/WebCore/dom/BroadcastChannel.h

    r282105 r282366  
    5757    using RefCounted<BroadcastChannel>::deref;
    5858
    59     BroadcastChannelIdentifier identifier() const { return m_identifier; }
    60     const String& name() const { return m_name; }
     59    BroadcastChannelIdentifier identifier() const;
     60    String name() const;
    6161
    6262    ExceptionOr<void> postMessage(JSC::JSGlobalObject&, JSC::JSValue message);
     
    8383    void stop() final { close(); }
    8484
    85     const String m_name;
    86     const ClientOrigin m_origin;
    87     const BroadcastChannelIdentifier m_identifier;
     85    class MainThreadBridge;
     86    Ref<MainThreadBridge> m_mainThreadBridge;
    8887    bool m_isClosed { false };
    8988    bool m_hasRelevantEventListener { false };
  • trunk/Source/WebCore/page/ChromeClient.h

    r281980 r282366  
    555555    virtual void hasStorageAccess(RegistrableDomain&& /*subFrameDomain*/, RegistrableDomain&& /*topFrameDomain*/, Frame&, WTF::CompletionHandler<void(bool)>&& completionHandler) { completionHandler(false); }
    556556    virtual void requestStorageAccess(RegistrableDomain&& subFrameDomain, RegistrableDomain&& topFrameDomain, Frame&, StorageAccessScope scope, WTF::CompletionHandler<void(RequestStorageAccessResult)>&& completionHandler) { completionHandler({ StorageAccessWasGranted::No, StorageAccessPromptWasShown::No, scope, WTFMove(topFrameDomain), WTFMove(subFrameDomain) }); }
     557    virtual bool hasPageLevelStorageAccess(const RegistrableDomain& /*topLevelDomain*/, const RegistrableDomain& /*resourceDomain*/) const { return false; }
    557558#endif
    558559
  • trunk/Source/WebKit/ChangeLog

    r282365 r282366  
     12021-09-13  Chris Dumez  <cdumez@apple.com>
     2
     3        Relax BroadcastChannel origin partitioning if iframe has storage access
     4        https://bugs.webkit.org/show_bug.cgi?id=230164
     5
     6        Reviewed by Alex Christensen.
     7
     8        * WebProcess/WebCoreSupport/WebChromeClient.cpp:
     9        (WebKit::WebChromeClient::hasPageLevelStorageAccess const):
     10        * WebProcess/WebCoreSupport/WebChromeClient.h:
     11
    1122021-09-13  Chris Dumez  <cdumez@apple.com>
    213
  • trunk/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp

    r281980 r282366  
    14421442    m_page.requestStorageAccess(WTFMove(subFrameDomain), WTFMove(topFrameDomain), *webFrame, scope, WTFMove(completionHandler));
    14431443}
     1444
     1445bool WebChromeClient::hasPageLevelStorageAccess(const WebCore::RegistrableDomain& topLevelDomain, const WebCore::RegistrableDomain& resourceDomain) const
     1446{
     1447    return m_page.hasPageLevelStorageAccess(topLevelDomain, resourceDomain);
     1448}
    14441449#endif
    14451450
  • trunk/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.h

    r281980 r282366  
    411411    void hasStorageAccess(WebCore::RegistrableDomain&& subFrameDomain, WebCore::RegistrableDomain&& topFrameDomain, WebCore::Frame&, WTF::CompletionHandler<void(bool)>&&) final;
    412412    void requestStorageAccess(WebCore::RegistrableDomain&& subFrameDomain, WebCore::RegistrableDomain&& topFrameDomain, WebCore::Frame&, WebCore::StorageAccessScope, WTF::CompletionHandler<void(WebCore::RequestStorageAccessResult)>&&) final;
     413    bool hasPageLevelStorageAccess(const WebCore::RegistrableDomain& topLevelDomain, const WebCore::RegistrableDomain& resourceDomain) const final;
    413414#endif
    414415
Note: See TracChangeset for help on using the changeset viewer.