tor-browser

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

PermissionDelegateHandler.cpp (13276B)


      1 /* -*- Mode: C++; tab-width: 2; 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 "mozilla/PermissionDelegateHandler.h"
      8 
      9 #include "nsPIDOMWindow.h"
     10 #include "nsIPrincipal.h"
     11 #include "nsContentPermissionHelper.h"
     12 
     13 #include "mozilla/BasePrincipal.h"
     14 #include "mozilla/StaticPrefs_permissions.h"
     15 #include "mozilla/dom/BrowsingContext.h"
     16 #include "mozilla/dom/Document.h"
     17 #include "mozilla/dom/FeaturePolicyUtils.h"
     18 #include "mozilla/dom/WindowContext.h"
     19 #include "mozilla/PermissionManager.h"
     20 
     21 using namespace mozilla::dom;
     22 
     23 namespace mozilla {
     24 
     25 typedef PermissionDelegateHandler::PermissionDelegatePolicy DelegatePolicy;
     26 typedef PermissionDelegateHandler::PermissionDelegateInfo DelegateInfo;
     27 
     28 // Particular type of permissions to care about. We decide cases by case and
     29 // give various types of controls over each of these.
     30 static const DelegateInfo sPermissionsMap[] = {
     31    // Permissions API map. All permission names have to be in lowercase.
     32    {"geo", u"geolocation", DelegatePolicy::eDelegateUseFeaturePolicy},
     33    // The same with geo, but we support both to save some conversions between
     34    // "geo" and "geolocation"
     35    {"geolocation", u"geolocation", DelegatePolicy::eDelegateUseFeaturePolicy},
     36    {"desktop-notification", nullptr,
     37     DelegatePolicy::ePersistDeniedCrossOrigin},
     38    {"persistent-storage", nullptr, DelegatePolicy::ePersistDeniedCrossOrigin},
     39    {"vibration", nullptr, DelegatePolicy::ePersistDeniedCrossOrigin},
     40    {"midi", nullptr, DelegatePolicy::eDelegateUseIframeOrigin},
     41    // Like "midi" but with sysex support.
     42    {"midi-sysex", nullptr, DelegatePolicy::eDelegateUseIframeOrigin},
     43    {"storage-access", nullptr, DelegatePolicy::eDelegateUseIframeOrigin},
     44    {"camera", u"camera", DelegatePolicy::eDelegateUseFeaturePolicy},
     45    {"microphone", u"microphone", DelegatePolicy::eDelegateUseFeaturePolicy},
     46    {"screen", u"display-capture", DelegatePolicy::eDelegateUseFeaturePolicy},
     47    {"xr", u"xr-spatial-tracking", DelegatePolicy::eDelegateUseFeaturePolicy},
     48    {"localhost", u"localhost", DelegatePolicy::eDelegateUseFeaturePolicy},
     49    {"local-network", u"local-network",
     50     DelegatePolicy::eDelegateUseFeaturePolicy},
     51 
     52    {"screen-wake-lock", u"screen-wake-lock",
     53     DelegatePolicy::eDelegateUseFeaturePolicy}};
     54 
     55 static_assert(PermissionDelegateHandler::DELEGATED_PERMISSION_COUNT ==
     56                  (sizeof(sPermissionsMap) / sizeof(DelegateInfo)),
     57              "The PermissionDelegateHandler::DELEGATED_PERMISSION_COUNT must "
     58              "match to the "
     59              "length of sPermissionsMap. Please update it.");
     60 
     61 NS_IMPL_CYCLE_COLLECTION(PermissionDelegateHandler)
     62 NS_IMPL_CYCLE_COLLECTING_ADDREF(PermissionDelegateHandler)
     63 NS_IMPL_CYCLE_COLLECTING_RELEASE(PermissionDelegateHandler)
     64 
     65 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PermissionDelegateHandler)
     66  NS_INTERFACE_MAP_ENTRY(nsIPermissionDelegateHandler)
     67  NS_INTERFACE_MAP_ENTRY(nsISupports)
     68 NS_INTERFACE_MAP_END
     69 
     70 PermissionDelegateHandler::PermissionDelegateHandler(dom::Document* aDocument)
     71    : mDocument(aDocument) {
     72  MOZ_ASSERT(aDocument);
     73 }
     74 
     75 /* static */
     76 const DelegateInfo* PermissionDelegateHandler::GetPermissionDelegateInfo(
     77    const nsAString& aPermissionName) {
     78  nsAutoString lowerContent(aPermissionName);
     79  ToLowerCase(lowerContent);
     80 
     81  for (const auto& perm : sPermissionsMap) {
     82    if (lowerContent.EqualsASCII(perm.mPermissionName)) {
     83      return &perm;
     84    }
     85  }
     86 
     87  return nullptr;
     88 }
     89 
     90 NS_IMETHODIMP
     91 PermissionDelegateHandler::MaybeUnsafePermissionDelegate(
     92    const nsTArray<nsCString>& aTypes, bool* aMaybeUnsafe) {
     93  *aMaybeUnsafe = false;
     94  for (auto& type : aTypes) {
     95    const DelegateInfo* info =
     96        GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(type));
     97    if (!info) {
     98      continue;
     99    }
    100 
    101    nsAutoString featureName(info->mFeatureName);
    102    if (FeaturePolicyUtils::IsFeatureUnsafeAllowedAll(mDocument, featureName)) {
    103      *aMaybeUnsafe = true;
    104      return NS_OK;
    105    }
    106  }
    107 
    108  return NS_OK;
    109 }
    110 
    111 /* static */
    112 nsresult PermissionDelegateHandler::GetDelegatePrincipal(
    113    const nsACString& aType, nsIContentPermissionRequest* aRequest,
    114    nsIPrincipal** aResult) {
    115  MOZ_ASSERT(aRequest);
    116 
    117  const DelegateInfo* info =
    118      GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(aType));
    119  if (!info) {
    120    *aResult = nullptr;
    121    return NS_OK;
    122  }
    123 
    124  if (info->mPolicy == DelegatePolicy::eDelegateUseTopOrigin ||
    125      info->mPolicy == DelegatePolicy::eDelegateUseFeaturePolicy) {
    126    return aRequest->GetTopLevelPrincipal(aResult);
    127  }
    128 
    129  return aRequest->GetPrincipal(aResult);
    130 }
    131 
    132 bool PermissionDelegateHandler::Initialize() {
    133  MOZ_ASSERT(mDocument);
    134 
    135  mPermissionManager = PermissionManager::GetInstance();
    136  if (!mPermissionManager) {
    137    return false;
    138  }
    139 
    140  mPrincipal = mDocument->NodePrincipal();
    141  return true;
    142 }
    143 
    144 static bool IsCrossOriginContentToTop(Document* aDocument) {
    145  MOZ_ASSERT(aDocument);
    146 
    147  RefPtr<BrowsingContext> bc = aDocument->GetBrowsingContext();
    148  if (!bc) {
    149    return true;
    150  }
    151  RefPtr<BrowsingContext> topBC = bc->Top();
    152 
    153  // In Fission, we can know if it is cross-origin by checking whether both
    154  // contexts in the same process. So, If they are not in the same process, we
    155  // can say that it's cross-origin.
    156  if (!topBC->IsInProcess()) {
    157    return true;
    158  }
    159 
    160  RefPtr<Document> topDoc = topBC->GetDocument();
    161  if (!topDoc) {
    162    return true;
    163  }
    164 
    165  nsCOMPtr<nsIPrincipal> topLevelPrincipal = topDoc->NodePrincipal();
    166 
    167  return !aDocument->NodePrincipal()->Subsumes(topLevelPrincipal);
    168 }
    169 
    170 bool PermissionDelegateHandler::HasFeaturePolicyAllowed(
    171    const DelegateInfo* info) const {
    172  if (info->mPolicy != DelegatePolicy::eDelegateUseFeaturePolicy ||
    173      !info->mFeatureName) {
    174    return true;
    175  }
    176 
    177  nsAutoString featureName(info->mFeatureName);
    178  return FeaturePolicyUtils::IsFeatureAllowed(mDocument, featureName);
    179 }
    180 
    181 bool PermissionDelegateHandler::HasPermissionDelegated(
    182    const nsACString& aType) const {
    183  MOZ_ASSERT(mDocument);
    184 
    185  // System principal should have right to make permission request
    186  if (mPrincipal->IsSystemPrincipal()) {
    187    return true;
    188  }
    189 
    190  const DelegateInfo* info =
    191      GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(aType));
    192  if (!info || !HasFeaturePolicyAllowed(info)) {
    193    return false;
    194  }
    195 
    196  if (info->mPolicy == DelegatePolicy::ePersistDeniedCrossOrigin &&
    197      !mDocument->IsTopLevelContentDocument() &&
    198      IsCrossOriginContentToTop(mDocument)) {
    199    return false;
    200  }
    201 
    202  return true;
    203 }
    204 
    205 nsresult PermissionDelegateHandler::GetPermission(const nsACString& aType,
    206                                                  uint32_t* aPermission,
    207                                                  bool aExactHostMatch) {
    208  MOZ_ASSERT(mDocument);
    209  MOZ_ASSERT(mPrincipal);
    210 
    211  if (mPrincipal->IsSystemPrincipal()) {
    212    *aPermission = nsIPermissionManager::ALLOW_ACTION;
    213    return NS_OK;
    214  }
    215 
    216  const DelegateInfo* info =
    217      GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(aType));
    218  if (!info || !HasFeaturePolicyAllowed(info)) {
    219    *aPermission = nsIPermissionManager::DENY_ACTION;
    220    return NS_OK;
    221  }
    222 
    223  nsresult (NS_STDCALL nsIPermissionManager::*testPermission)(
    224      nsIPrincipal*, const nsACString&, uint32_t*) =
    225      aExactHostMatch ? &nsIPermissionManager::TestExactPermissionFromPrincipal
    226                      : &nsIPermissionManager::TestPermissionFromPrincipal;
    227 
    228  if (info->mPolicy == DelegatePolicy::ePersistDeniedCrossOrigin &&
    229      !mDocument->IsTopLevelContentDocument() &&
    230      IsCrossOriginContentToTop(mDocument)) {
    231    *aPermission = nsIPermissionManager::DENY_ACTION;
    232    return NS_OK;
    233  }
    234 
    235  nsIPrincipal* principal = mPrincipal;
    236  // If we cannot get the browsing context from the document, we fallback to use
    237  // the prinicpal of the document to test the permission.
    238  RefPtr<BrowsingContext> bc = mDocument->GetBrowsingContext();
    239 
    240  if ((info->mPolicy == DelegatePolicy::eDelegateUseTopOrigin ||
    241       info->mPolicy == DelegatePolicy::eDelegateUseFeaturePolicy) &&
    242      bc) {
    243    RefPtr<WindowContext> topWC = bc->GetTopWindowContext();
    244 
    245    if (topWC && topWC->IsInProcess()) {
    246      // If the top-level window context is in the same process, we directly get
    247      // the node principal from the top-level document to test the permission.
    248      // We cannot check the lists in the window context in this case since the
    249      // 'perm-changed' could be notified in the iframe before the top-level in
    250      // certain cases, for example, request permissions in first-party iframes.
    251      // In this case, the list in window context hasn't gotten updated, so it
    252      // would has an out-dated value until the top-level window get the
    253      // observer. So, we have to test permission manager directly if we can.
    254      RefPtr<Document> topDoc = topWC->GetBrowsingContext()->GetDocument();
    255 
    256      if (topDoc) {
    257        principal = topDoc->NodePrincipal();
    258      }
    259    } else if (topWC) {
    260      // Get the delegated permissions from the top-level window context.
    261      DelegatedPermissionList list =
    262          aExactHostMatch ? topWC->GetDelegatedExactHostMatchPermissions()
    263                          : topWC->GetDelegatedPermissions();
    264      size_t idx = std::distance(sPermissionsMap, info);
    265      *aPermission = list.mPermissions[idx];
    266      return NS_OK;
    267    }
    268  }
    269 
    270  return (mPermissionManager->*testPermission)(principal, aType, aPermission);
    271 }
    272 
    273 nsresult PermissionDelegateHandler::GetPermissionForPermissionsAPI(
    274    const nsACString& aType, uint32_t* aPermission) {
    275  return GetPermission(aType, aPermission, false);
    276 }
    277 
    278 void PermissionDelegateHandler::PopulateAllDelegatedPermissions() {
    279  MOZ_ASSERT(mDocument);
    280  MOZ_ASSERT(mPermissionManager);
    281 
    282  // We only populate the delegated permissions for the top-level content.
    283  if (!mDocument->IsTopLevelContentDocument()) {
    284    return;
    285  }
    286 
    287  RefPtr<WindowContext> wc = mDocument->GetWindowContext();
    288  NS_ENSURE_TRUE_VOID(wc && !wc->IsDiscarded());
    289 
    290  DelegatedPermissionList list;
    291  DelegatedPermissionList exactHostMatchList;
    292 
    293  for (const auto& perm : sPermissionsMap) {
    294    size_t idx = std::distance(sPermissionsMap, &perm);
    295 
    296    nsDependentCString type(perm.mPermissionName);
    297    // Populate the permission.
    298    uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
    299    (void)mPermissionManager->TestPermissionFromPrincipal(mPrincipal, type,
    300                                                          &permission);
    301    list.mPermissions[idx] = permission;
    302 
    303    // Populate the exact-host-match permission.
    304    permission = nsIPermissionManager::UNKNOWN_ACTION;
    305    (void)mPermissionManager->TestExactPermissionFromPrincipal(mPrincipal, type,
    306                                                               &permission);
    307    exactHostMatchList.mPermissions[idx] = permission;
    308  }
    309 
    310  WindowContext::Transaction txn;
    311  txn.SetDelegatedPermissions(list);
    312  txn.SetDelegatedExactHostMatchPermissions(exactHostMatchList);
    313  MOZ_ALWAYS_SUCCEEDS(txn.Commit(wc));
    314 }
    315 
    316 void PermissionDelegateHandler::UpdateDelegatedPermission(
    317    const nsACString& aType) {
    318  MOZ_ASSERT(mDocument);
    319  MOZ_ASSERT(mPermissionManager);
    320 
    321  // We only update the delegated permission for the top-level content.
    322  if (!mDocument->IsTopLevelContentDocument()) {
    323    return;
    324  }
    325 
    326  RefPtr<WindowContext> wc = mDocument->GetWindowContext();
    327  NS_ENSURE_TRUE_VOID(wc);
    328 
    329  const DelegateInfo* info =
    330      GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(aType));
    331  if (!info) {
    332    return;
    333  }
    334  size_t idx = std::distance(sPermissionsMap, info);
    335 
    336  WindowContext::Transaction txn;
    337  bool changed = false;
    338  DelegatedPermissionList list = wc->GetDelegatedPermissions();
    339 
    340  if (UpdateDelegatePermissionInternal(
    341          list, aType, idx,
    342          &nsIPermissionManager::TestPermissionFromPrincipal)) {
    343    txn.SetDelegatedPermissions(list);
    344    changed = true;
    345  }
    346 
    347  DelegatedPermissionList exactHostMatchList =
    348      wc->GetDelegatedExactHostMatchPermissions();
    349 
    350  if (UpdateDelegatePermissionInternal(
    351          exactHostMatchList, aType, idx,
    352          &nsIPermissionManager::TestExactPermissionFromPrincipal)) {
    353    txn.SetDelegatedExactHostMatchPermissions(exactHostMatchList);
    354    changed = true;
    355  }
    356 
    357  // We only commit if there is any change of permissions.
    358  if (changed) {
    359    MOZ_ALWAYS_SUCCEEDS(txn.Commit(wc));
    360  }
    361 }
    362 
    363 bool PermissionDelegateHandler::UpdateDelegatePermissionInternal(
    364    PermissionDelegateHandler::DelegatedPermissionList& aList,
    365    const nsACString& aType, size_t aIdx,
    366    nsresult (NS_STDCALL nsIPermissionManager::*aTestFunc)(nsIPrincipal*,
    367                                                           const nsACString&,
    368                                                           uint32_t*)) {
    369  MOZ_ASSERT(aTestFunc);
    370  MOZ_ASSERT(mPermissionManager);
    371  MOZ_ASSERT(mPrincipal);
    372 
    373  uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
    374  (void)(mPermissionManager->*aTestFunc)(mPrincipal, aType, &permission);
    375 
    376  if (aList.mPermissions[aIdx] != permission) {
    377    aList.mPermissions[aIdx] = permission;
    378    return true;
    379  }
    380 
    381  return false;
    382 }
    383 
    384 }  // namespace mozilla