tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ClientChannelHelper.cpp (16245B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "ClientChannelHelper.h"
      8 
      9 #include "ClientManager.h"
     10 #include "ClientSource.h"
     11 #include "MainThreadUtils.h"
     12 #include "mozilla/StaticPrefs_privacy.h"
     13 #include "mozilla/StoragePrincipalHelper.h"
     14 #include "mozilla/dom/ClientsBinding.h"
     15 #include "mozilla/dom/ServiceWorkerDescriptor.h"
     16 #include "mozilla/ipc/BackgroundUtils.h"
     17 #include "nsContentUtils.h"
     18 #include "nsIAsyncVerifyRedirectCallback.h"
     19 #include "nsIChannel.h"
     20 #include "nsIChannelEventSink.h"
     21 #include "nsIHttpChannelInternal.h"
     22 #include "nsIInterfaceRequestor.h"
     23 #include "nsIInterfaceRequestorUtils.h"
     24 
     25 namespace mozilla::dom {
     26 
     27 using mozilla::ipc::PrincipalInfoToPrincipal;
     28 
     29 namespace {
     30 
     31 // In the default mode, ClientChannelHelper runs in the content process and
     32 // handles all redirects. When we use DocumentChannel, redirects aren't exposed
     33 // to the content process, so we run an instance of this in both processes, one
     34 // to handle redirects in the parent and one to handle the final channel
     35 // replacement (DocumentChannelChild 'redirects' to the final channel) in the
     36 // child.
     37 
     38 class ClientChannelHelper : public nsIInterfaceRequestor,
     39                            public nsIChannelEventSink {
     40 protected:
     41  nsCOMPtr<nsIInterfaceRequestor> mOuter;
     42  nsCOMPtr<nsISerialEventTarget> mEventTarget;
     43 
     44  virtual ~ClientChannelHelper() = default;
     45 
     46  NS_IMETHOD
     47  GetInterface(const nsIID& aIID, void** aResultOut) override {
     48    if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
     49      *aResultOut = static_cast<nsIChannelEventSink*>(this);
     50      NS_ADDREF_THIS();
     51      return NS_OK;
     52    }
     53 
     54    if (mOuter) {
     55      return mOuter->GetInterface(aIID, aResultOut);
     56    }
     57 
     58    return NS_ERROR_NO_INTERFACE;
     59  }
     60 
     61  virtual void CreateClient(nsILoadInfo* aLoadInfo, nsIPrincipal* aPrincipal) {
     62    CreateClientForPrincipal(aLoadInfo, aPrincipal, mEventTarget);
     63  }
     64 
     65  NS_IMETHOD
     66  AsyncOnChannelRedirect(nsIChannel* aOldChannel, nsIChannel* aNewChannel,
     67                         uint32_t aFlags,
     68                         nsIAsyncVerifyRedirectCallback* aCallback) override {
     69    MOZ_ASSERT(NS_IsMainThread());
     70 
     71    nsresult rv = nsContentUtils::CheckSameOrigin(aOldChannel, aNewChannel);
     72    if (NS_WARN_IF(NS_FAILED(rv) && rv != NS_ERROR_DOM_BAD_URI)) {
     73      return rv;
     74    }
     75 
     76    nsCOMPtr<nsILoadInfo> oldLoadInfo = aOldChannel->LoadInfo();
     77    nsCOMPtr<nsILoadInfo> newLoadInfo = aNewChannel->LoadInfo();
     78 
     79    UniquePtr<ClientSource> reservedClient =
     80        oldLoadInfo->TakeReservedClientSource();
     81 
     82    // If its a same-origin redirect we just move our reserved client to the
     83    // new channel.
     84    if (NS_SUCCEEDED(rv)) {
     85      if (reservedClient) {
     86        newLoadInfo->GiveReservedClientSource(std::move(reservedClient));
     87      }
     88 
     89      // It seems sometimes necko passes two channels with the same LoadInfo.
     90      // We only need to move the reserved/initial ClientInfo over if we
     91      // actually have a different LoadInfo.
     92      else if (oldLoadInfo != newLoadInfo) {
     93        const Maybe<ClientInfo>& reservedClientInfo =
     94            oldLoadInfo->GetReservedClientInfo();
     95 
     96        const Maybe<ClientInfo>& initialClientInfo =
     97            oldLoadInfo->GetInitialClientInfo();
     98 
     99        MOZ_DIAGNOSTIC_ASSERT(reservedClientInfo.isNothing() ||
    100                              initialClientInfo.isNothing());
    101 
    102        if (reservedClientInfo.isSome()) {
    103          // Create a new client for the case the controller is cleared for the
    104          // new loadInfo. ServiceWorkerManager::DispatchFetchEvent() called
    105          // ServiceWorkerManager::StartControllingClient() making the old
    106          // client to be controlled eventually. However, the controller setting
    107          // propagation to the child process could happen later than
    108          // nsGlobalWindowInner::EnsureClientSource(), such that
    109          // nsGlobalWindowInner will be controlled as unexpected.
    110          if (oldLoadInfo->GetController().isSome() &&
    111              newLoadInfo->GetController().isNothing()) {
    112            nsCOMPtr<nsIPrincipal> foreignPartitionedPrincipal;
    113            rv = StoragePrincipalHelper::GetPrincipal(
    114                aNewChannel,
    115                StaticPrefs::privacy_partition_serviceWorkers()
    116                    ? StoragePrincipalHelper::eForeignPartitionedPrincipal
    117                    : StoragePrincipalHelper::eRegularPrincipal,
    118                getter_AddRefs(foreignPartitionedPrincipal));
    119            NS_ENSURE_SUCCESS(rv, rv);
    120            reservedClient.reset();
    121            CreateClient(newLoadInfo, foreignPartitionedPrincipal);
    122          } else {
    123            newLoadInfo->SetReservedClientInfo(reservedClientInfo.ref());
    124          }
    125        }
    126 
    127        if (initialClientInfo.isSome()) {
    128          newLoadInfo->SetInitialClientInfo(initialClientInfo.ref());
    129        }
    130      }
    131    }
    132 
    133    // If it's a cross-origin redirect then we discard the old reserved client
    134    // and create a new one.
    135    else {
    136      nsCOMPtr<nsIPrincipal> foreignPartitionedPrincipal;
    137      rv = StoragePrincipalHelper::GetPrincipal(
    138          aNewChannel,
    139          StaticPrefs::privacy_partition_serviceWorkers()
    140              ? StoragePrincipalHelper::eForeignPartitionedPrincipal
    141              : StoragePrincipalHelper::eRegularPrincipal,
    142          getter_AddRefs(foreignPartitionedPrincipal));
    143      NS_ENSURE_SUCCESS(rv, rv);
    144 
    145      reservedClient.reset();
    146      CreateClient(newLoadInfo, foreignPartitionedPrincipal);
    147    }
    148 
    149    uint32_t redirectMode = nsIHttpChannelInternal::REDIRECT_MODE_MANUAL;
    150    nsCOMPtr<nsIHttpChannelInternal> http = do_QueryInterface(aOldChannel);
    151    if (http) {
    152      MOZ_ALWAYS_SUCCEEDS(http->GetRedirectMode(&redirectMode));
    153    }
    154 
    155    // Normally we keep the controller across channel redirects, but we must
    156    // clear it when a document load redirects.  Only do this for real
    157    // redirects, however.
    158    //
    159    // This is effectively described in step 4.2 of:
    160    //
    161    //  https://fetch.spec.whatwg.org/#http-fetch
    162    //
    163    // The spec sets the service-workers mode to none when the request is
    164    // configured to *not* follow redirects.  This prevents any further
    165    // service workers from intercepting.  The first service worker that
    166    // had a shot at the FetchEvent remains the controller in this case.
    167    if (!(aFlags & nsIChannelEventSink::REDIRECT_INTERNAL) &&
    168        redirectMode != nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW) {
    169      newLoadInfo->ClearController();
    170    }
    171 
    172    nsCOMPtr<nsIChannelEventSink> outerSink = do_GetInterface(mOuter);
    173    if (outerSink) {
    174      return outerSink->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags,
    175                                               aCallback);
    176    }
    177 
    178    aCallback->OnRedirectVerifyCallback(NS_OK);
    179    return NS_OK;
    180  }
    181 
    182 public:
    183  ClientChannelHelper(nsIInterfaceRequestor* aOuter,
    184                      nsISerialEventTarget* aEventTarget)
    185      : mOuter(aOuter), mEventTarget(aEventTarget) {}
    186 
    187  NS_DECL_ISUPPORTS
    188 
    189  virtual void CreateClientForPrincipal(nsILoadInfo* aLoadInfo,
    190                                        nsIPrincipal* aPrincipal,
    191                                        nsISerialEventTarget* aEventTarget) {
    192    // Create the new ClientSource.  This should only happen for window
    193    // Clients since support cross-origin redirects are blocked by the
    194    // same-origin security policy.
    195    UniquePtr<ClientSource> reservedClient = ClientManager::CreateSource(
    196        ClientType::Window, aEventTarget, aPrincipal);
    197    MOZ_DIAGNOSTIC_ASSERT(reservedClient);
    198 
    199    aLoadInfo->GiveReservedClientSource(std::move(reservedClient));
    200  }
    201 };
    202 
    203 NS_IMPL_ISUPPORTS(ClientChannelHelper, nsIInterfaceRequestor,
    204                  nsIChannelEventSink);
    205 
    206 class ClientChannelHelperParent final : public ClientChannelHelper {
    207  ~ClientChannelHelperParent() {
    208    // This requires that if a load completes, the associated ClientSource is
    209    // created and registers itself before this ClientChannelHelperParent is
    210    // destroyed. Otherwise, we may incorrectly "forget" a future ClientSource
    211    // which will actually be created.
    212    SetFutureSourceInfo(Nothing());
    213  }
    214 
    215  void CreateClient(nsILoadInfo* aLoadInfo, nsIPrincipal* aPrincipal) override {
    216    CreateClientForPrincipal(aLoadInfo, aPrincipal, mEventTarget);
    217  }
    218 
    219  void SetFutureSourceInfo(Maybe<ClientInfo>&& aClientInfo) {
    220    if (mRecentFutureSourceInfo) {
    221      // No-op if the corresponding ClientSource has alrady been created, but
    222      // it's not known if that's the case here.
    223      ClientManager::ForgetFutureSource(*mRecentFutureSourceInfo);
    224    }
    225 
    226    if (aClientInfo) {
    227      (void)NS_WARN_IF(!ClientManager::ExpectFutureSource(*aClientInfo));
    228    }
    229 
    230    mRecentFutureSourceInfo = std::move(aClientInfo);
    231  }
    232 
    233  // Keep track of the most recent ClientInfo created which isn't backed by a
    234  // ClientSource, which is used to notify ClientManagerService that the
    235  // ClientSource won't ever actually be constructed.
    236  Maybe<ClientInfo> mRecentFutureSourceInfo;
    237 
    238 public:
    239  void CreateClientForPrincipal(nsILoadInfo* aLoadInfo,
    240                                nsIPrincipal* aPrincipal,
    241                                nsISerialEventTarget* aEventTarget) override {
    242    // If we're managing redirects in the parent, then we don't want
    243    // to create a new ClientSource (since those need to live with
    244    // the global), so just allocate a new ClientInfo/id and we can
    245    // create a ClientSource when the final channel propagates back
    246    // to the child.
    247    Maybe<ClientInfo> reservedInfo =
    248        ClientManager::CreateInfo(ClientType::Window, aPrincipal);
    249    if (reservedInfo) {
    250      aLoadInfo->SetReservedClientInfo(*reservedInfo);
    251      SetFutureSourceInfo(std::move(reservedInfo));
    252    }
    253  }
    254  ClientChannelHelperParent(nsIInterfaceRequestor* aOuter,
    255                            nsISerialEventTarget* aEventTarget)
    256      : ClientChannelHelper(aOuter, nullptr) {}
    257 };
    258 
    259 class ClientChannelHelperChild final : public ClientChannelHelper {
    260  ~ClientChannelHelperChild() = default;
    261 
    262  NS_IMETHOD
    263  AsyncOnChannelRedirect(nsIChannel* aOldChannel, nsIChannel* aNewChannel,
    264                         uint32_t aFlags,
    265                         nsIAsyncVerifyRedirectCallback* aCallback) override {
    266    MOZ_ASSERT(NS_IsMainThread());
    267 
    268    // All ClientInfo allocation should have been handled in the parent process
    269    // by ClientChannelHelperParent, so the only remaining thing to do is to
    270    // allocate a ClientSource around the ClientInfo on the channel.
    271    CreateReservedSourceIfNeeded(aNewChannel, mEventTarget);
    272 
    273    nsCOMPtr<nsIChannelEventSink> outerSink = do_GetInterface(mOuter);
    274    if (outerSink) {
    275      return outerSink->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags,
    276                                               aCallback);
    277    }
    278 
    279    aCallback->OnRedirectVerifyCallback(NS_OK);
    280    return NS_OK;
    281  }
    282 
    283 public:
    284  ClientChannelHelperChild(nsIInterfaceRequestor* aOuter,
    285                           nsISerialEventTarget* aEventTarget)
    286      : ClientChannelHelper(aOuter, aEventTarget) {}
    287 };
    288 
    289 }  // anonymous namespace
    290 
    291 template <typename T>
    292 nsresult AddClientChannelHelperInternal(nsIChannel* aChannel,
    293                                        Maybe<ClientInfo>&& aReservedClientInfo,
    294                                        Maybe<ClientInfo>&& aInitialClientInfo,
    295                                        nsISerialEventTarget* aEventTarget) {
    296  MOZ_ASSERT(NS_IsMainThread());
    297 
    298  Maybe<ClientInfo> initialClientInfo(std::move(aInitialClientInfo));
    299  Maybe<ClientInfo> reservedClientInfo(std::move(aReservedClientInfo));
    300  MOZ_DIAGNOSTIC_ASSERT(reservedClientInfo.isNothing() ||
    301                        initialClientInfo.isNothing());
    302 
    303  nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
    304 
    305  nsCOMPtr<nsIPrincipal> channelForeignPartitionedPrincipal;
    306  nsresult rv = StoragePrincipalHelper::GetPrincipal(
    307      aChannel,
    308      StaticPrefs::privacy_partition_serviceWorkers()
    309          ? StoragePrincipalHelper::eForeignPartitionedPrincipal
    310          : StoragePrincipalHelper::eRegularPrincipal,
    311      getter_AddRefs(channelForeignPartitionedPrincipal));
    312  NS_ENSURE_SUCCESS(rv, rv);
    313 
    314  // Only allow the initial ClientInfo to be set if the current channel
    315  // principal matches.
    316  if (initialClientInfo.isSome()) {
    317    auto initialPrincipalOrErr =
    318        PrincipalInfoToPrincipal(initialClientInfo.ref().PrincipalInfo());
    319 
    320    bool equals = false;
    321    rv = initialPrincipalOrErr.isErr()
    322             ? initialPrincipalOrErr.unwrapErr()
    323             : initialPrincipalOrErr.unwrap()->Equals(
    324                   channelForeignPartitionedPrincipal, &equals);
    325    if (NS_FAILED(rv) || !equals) {
    326      initialClientInfo.reset();
    327    }
    328  }
    329 
    330  // Only allow the reserved ClientInfo to be set if the current channel
    331  // principal matches.
    332  if (reservedClientInfo.isSome()) {
    333    auto reservedPrincipalOrErr =
    334        PrincipalInfoToPrincipal(reservedClientInfo.ref().PrincipalInfo());
    335 
    336    bool equals = false;
    337    rv = reservedPrincipalOrErr.isErr()
    338             ? reservedPrincipalOrErr.unwrapErr()
    339             : reservedPrincipalOrErr.unwrap()->Equals(
    340                   channelForeignPartitionedPrincipal, &equals);
    341    if (NS_FAILED(rv) || !equals) {
    342      reservedClientInfo.reset();
    343    }
    344  }
    345 
    346  nsCOMPtr<nsIInterfaceRequestor> outerCallbacks;
    347  rv = aChannel->GetNotificationCallbacks(getter_AddRefs(outerCallbacks));
    348  NS_ENSURE_SUCCESS(rv, rv);
    349 
    350  RefPtr<ClientChannelHelper> helper = new T(outerCallbacks, aEventTarget);
    351 
    352  if (initialClientInfo.isNothing() && reservedClientInfo.isNothing()) {
    353    helper->CreateClientForPrincipal(
    354        loadInfo, channelForeignPartitionedPrincipal, aEventTarget);
    355  }
    356 
    357  // Only set the callbacks helper if we are able to reserve the client
    358  // successfully.
    359  rv = aChannel->SetNotificationCallbacks(helper);
    360  NS_ENSURE_SUCCESS(rv, rv);
    361 
    362  if (initialClientInfo.isSome()) {
    363    loadInfo->SetInitialClientInfo(initialClientInfo.ref());
    364  }
    365 
    366  if (reservedClientInfo.isSome()) {
    367    loadInfo->SetReservedClientInfo(reservedClientInfo.ref());
    368  }
    369 
    370  return NS_OK;
    371 }
    372 
    373 nsresult AddClientChannelHelper(nsIChannel* aChannel,
    374                                Maybe<ClientInfo>&& aReservedClientInfo,
    375                                Maybe<ClientInfo>&& aInitialClientInfo,
    376                                nsISerialEventTarget* aEventTarget) {
    377  return AddClientChannelHelperInternal<ClientChannelHelper>(
    378      aChannel, std::move(aReservedClientInfo), std::move(aInitialClientInfo),
    379      aEventTarget);
    380 }
    381 
    382 nsresult AddClientChannelHelperInParent(
    383    nsIChannel* aChannel, Maybe<ClientInfo>&& aInitialClientInfo) {
    384  Maybe<ClientInfo> emptyReservedInfo;
    385  return AddClientChannelHelperInternal<ClientChannelHelperParent>(
    386      aChannel, std::move(emptyReservedInfo), std::move(aInitialClientInfo),
    387      nullptr);
    388 }
    389 
    390 nsresult AddClientChannelHelperInChild(nsIChannel* aChannel,
    391                                       nsISerialEventTarget* aEventTarget) {
    392  MOZ_ASSERT(NS_IsMainThread());
    393 
    394  nsCOMPtr<nsIInterfaceRequestor> outerCallbacks;
    395  nsresult rv =
    396      aChannel->GetNotificationCallbacks(getter_AddRefs(outerCallbacks));
    397  NS_ENSURE_SUCCESS(rv, rv);
    398 
    399  RefPtr<ClientChannelHelper> helper =
    400      new ClientChannelHelperChild(outerCallbacks, aEventTarget);
    401 
    402  // Only set the callbacks helper if we are able to reserve the client
    403  // successfully.
    404  rv = aChannel->SetNotificationCallbacks(helper);
    405  NS_ENSURE_SUCCESS(rv, rv);
    406 
    407  return NS_OK;
    408 }
    409 
    410 void CreateReservedSourceIfNeeded(nsIChannel* aChannel,
    411                                  nsISerialEventTarget* aEventTarget) {
    412  nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
    413  const Maybe<ClientInfo>& reservedClientInfo =
    414      loadInfo->GetReservedClientInfo();
    415 
    416  if (reservedClientInfo) {
    417    UniquePtr<ClientSource> reservedClient =
    418        ClientManager::CreateSourceFromInfo(*reservedClientInfo, aEventTarget);
    419    loadInfo->GiveReservedClientSource(std::move(reservedClient));
    420  }
    421 }
    422 
    423 }  // namespace mozilla::dom