tor-browser

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

MediaParent.cpp (16013B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set sw=2 ts=8 et ft=cpp : */
      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 file,
      5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "MediaParent.h"
      8 
      9 #include <mozilla/StaticMutex.h>
     10 
     11 #include "MediaEngine.h"
     12 #include "MediaUtils.h"
     13 #include "VideoUtils.h"
     14 #include "mozilla/Base64.h"
     15 #include "mozilla/Logging.h"
     16 #include "nsAppDirectoryServiceDefs.h"
     17 #include "nsClassHashtable.h"
     18 #include "nsIFile.h"
     19 #include "nsIInputStream.h"
     20 #include "nsILineInputStream.h"
     21 #include "nsIOutputStream.h"
     22 #include "nsISafeOutputStream.h"
     23 #include "nsISupportsImpl.h"
     24 #include "nsNetCID.h"
     25 #include "nsNetUtil.h"
     26 #include "nsThreadUtils.h"
     27 
     28 mozilla::LazyLogModule gMediaParentLog("MediaParent");
     29 #define LOG(args) MOZ_LOG(gMediaParentLog, mozilla::LogLevel::Debug, args)
     30 
     31 // A file in the profile dir is used to persist mOriginKeys used to anonymize
     32 // deviceIds to be unique per origin, to avoid them being supercookies.
     33 
     34 #define ORIGINKEYS_FILE u"enumerate_devices.txt"
     35 #define ORIGINKEYS_VERSION "1"
     36 
     37 namespace mozilla::media {
     38 
     39 StaticMutex sOriginKeyStoreStsMutex;
     40 
     41 class OriginKeyStore {
     42  NS_INLINE_DECL_REFCOUNTING(OriginKeyStore);
     43  class OriginKey {
     44   public:
     45    static const size_t DecodedLength = 18;
     46    static const size_t EncodedLength = DecodedLength * 4 / 3;
     47 
     48    explicit OriginKey(const nsACString& aKey,
     49                       int64_t aSecondsStamp = 0)  // 0 = temporal
     50        : mKey(aKey), mSecondsStamp(aSecondsStamp) {}
     51 
     52    nsCString mKey;  // Base64 encoded.
     53    int64_t mSecondsStamp;
     54  };
     55 
     56  class OriginKeysTable {
     57   public:
     58    OriginKeysTable() : mPersistCount(0) {}
     59 
     60    nsresult GetPrincipalKey(const ipc::PrincipalInfo& aPrincipalInfo,
     61                             nsCString& aResult, bool aPersist = false) {
     62      nsAutoCString principalString;
     63      PrincipalInfoToString(aPrincipalInfo, principalString);
     64 
     65      OriginKey* key;
     66      if (!mKeys.Get(principalString, &key)) {
     67        nsCString salt;  // Make a new one
     68        nsresult rv = GenerateRandomName(salt, OriginKey::EncodedLength);
     69        if (NS_WARN_IF(NS_FAILED(rv))) {
     70          return rv;
     71        }
     72        key = mKeys.InsertOrUpdate(principalString, MakeUnique<OriginKey>(salt))
     73                  .get();
     74      }
     75      if (aPersist && !key->mSecondsStamp) {
     76        key->mSecondsStamp = PR_Now() / PR_USEC_PER_SEC;
     77        mPersistCount++;
     78      }
     79      aResult = key->mKey;
     80      return NS_OK;
     81    }
     82 
     83    void Clear(int64_t aSinceWhen) {
     84      // Avoid int64_t* <-> void* casting offset
     85      OriginKey since(nsCString(), aSinceWhen / PR_USEC_PER_SEC);
     86      for (auto iter = mKeys.Iter(); !iter.Done(); iter.Next()) {
     87        auto originKey = iter.UserData();
     88        LOG((((originKey->mSecondsStamp >= since.mSecondsStamp)
     89                  ? "%s: REMOVE %" PRId64 " >= %" PRId64
     90                  : "%s: KEEP   %" PRId64 " < %" PRId64),
     91             __FUNCTION__, originKey->mSecondsStamp, since.mSecondsStamp));
     92 
     93        if (originKey->mSecondsStamp >= since.mSecondsStamp) {
     94          iter.Remove();
     95        }
     96      }
     97      mPersistCount = 0;
     98    }
     99 
    100   private:
    101    void PrincipalInfoToString(const ipc::PrincipalInfo& aPrincipalInfo,
    102                               nsACString& aString) {
    103      switch (aPrincipalInfo.type()) {
    104        case ipc::PrincipalInfo::TSystemPrincipalInfo:
    105          aString.AssignLiteral("[System Principal]");
    106          return;
    107 
    108        case ipc::PrincipalInfo::TNullPrincipalInfo: {
    109          const ipc::NullPrincipalInfo& info =
    110              aPrincipalInfo.get_NullPrincipalInfo();
    111          aString.Assign(info.spec());
    112          return;
    113        }
    114 
    115        case ipc::PrincipalInfo::TContentPrincipalInfo: {
    116          const ipc::ContentPrincipalInfo& info =
    117              aPrincipalInfo.get_ContentPrincipalInfo();
    118          aString.Assign(info.originNoSuffix());
    119 
    120          nsAutoCString suffix;
    121          info.attrs().CreateSuffix(suffix);
    122          aString.Append(suffix);
    123          return;
    124        }
    125 
    126        case ipc::PrincipalInfo::TExpandedPrincipalInfo: {
    127          const ipc::ExpandedPrincipalInfo& info =
    128              aPrincipalInfo.get_ExpandedPrincipalInfo();
    129 
    130          aString.AssignLiteral("[Expanded Principal [");
    131 
    132          for (uint32_t i = 0; i < info.allowlist().Length(); i++) {
    133            nsAutoCString str;
    134            PrincipalInfoToString(info.allowlist()[i], str);
    135 
    136            if (i != 0) {
    137              aString.AppendLiteral(", ");
    138            }
    139 
    140            aString.Append(str);
    141          }
    142 
    143          aString.AppendLiteral("]]");
    144          return;
    145        }
    146 
    147        default:
    148          MOZ_CRASH("Unknown PrincipalInfo type!");
    149      }
    150    }
    151 
    152   protected:
    153    nsClassHashtable<nsCStringHashKey, OriginKey> mKeys;
    154    size_t mPersistCount;
    155  };
    156 
    157  class OriginKeysLoader : public OriginKeysTable {
    158   public:
    159    OriginKeysLoader() = default;
    160 
    161    nsresult GetPrincipalKey(const ipc::PrincipalInfo& aPrincipalInfo,
    162                             nsCString& aResult, bool aPersist = false) {
    163      auto before = mPersistCount;
    164      nsresult rv =
    165          OriginKeysTable::GetPrincipalKey(aPrincipalInfo, aResult, aPersist);
    166      if (NS_WARN_IF(NS_FAILED(rv))) {
    167        return rv;
    168      }
    169 
    170      if (mPersistCount != before) {
    171        Save();
    172      }
    173      return NS_OK;
    174    }
    175 
    176    already_AddRefed<nsIFile> GetFile() {
    177      MOZ_ASSERT(mProfileDir);
    178      nsCOMPtr<nsIFile> file;
    179      nsresult rv = mProfileDir->Clone(getter_AddRefs(file));
    180      if (NS_WARN_IF(NS_FAILED(rv))) {
    181        return nullptr;
    182      }
    183      file->Append(nsLiteralString(ORIGINKEYS_FILE));
    184      return file.forget();
    185    }
    186 
    187    // Format of file is key secondsstamp origin (first line is version #):
    188    //
    189    // 1
    190    // rOMAAbFujNwKyIpj4RJ3Wt5Q 1424733961 http://fiddle.jshell.net
    191    // rOMAAbFujNwKyIpj4RJ3Wt5Q 1424734841 http://mozilla.github.io
    192    // etc.
    193 
    194    nsresult Read() {
    195      nsCOMPtr<nsIFile> file = GetFile();
    196      if (NS_WARN_IF(!file)) {
    197        return NS_ERROR_UNEXPECTED;
    198      }
    199      bool exists;
    200      nsresult rv = file->Exists(&exists);
    201      if (NS_WARN_IF(NS_FAILED(rv))) {
    202        return rv;
    203      }
    204      if (!exists) {
    205        return NS_OK;
    206      }
    207 
    208      nsCOMPtr<nsIInputStream> stream;
    209      rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file);
    210      if (NS_WARN_IF(NS_FAILED(rv))) {
    211        return rv;
    212      }
    213      nsCOMPtr<nsILineInputStream> i = do_QueryInterface(stream);
    214      MOZ_ASSERT(i);
    215      MOZ_ASSERT(!mPersistCount);
    216 
    217      nsCString line;
    218      bool hasMoreLines;
    219      rv = i->ReadLine(line, &hasMoreLines);
    220      if (NS_WARN_IF(NS_FAILED(rv))) {
    221        return rv;
    222      }
    223      if (!line.EqualsLiteral(ORIGINKEYS_VERSION)) {
    224        // If version on disk is newer than we can understand then ignore it.
    225        return NS_OK;
    226      }
    227 
    228      while (hasMoreLines) {
    229        rv = i->ReadLine(line, &hasMoreLines);
    230        if (NS_WARN_IF(NS_FAILED(rv))) {
    231          return rv;
    232        }
    233        // Read key secondsstamp origin.
    234        // Ignore any lines that don't fit format in the comment above exactly.
    235        int32_t f = line.FindChar(' ');
    236        if (f < 0) {
    237          continue;
    238        }
    239        const nsACString& key = Substring(line, 0, f);
    240        const nsACString& s = Substring(line, f + 1);
    241        f = s.FindChar(' ');
    242        if (f < 0) {
    243          continue;
    244        }
    245        int64_t secondsstamp = Substring(s, 0, f).ToInteger64(&rv);
    246        if (NS_FAILED(rv)) {
    247          continue;
    248        }
    249        const nsACString& origin = Substring(s, f + 1);
    250 
    251        // Validate key
    252        if (key.Length() != OriginKey::EncodedLength) {
    253          continue;
    254        }
    255        nsCString dummy;
    256        rv = Base64Decode(key, dummy);
    257        if (NS_FAILED(rv)) {
    258          continue;
    259        }
    260        mKeys.InsertOrUpdate(origin, MakeUnique<OriginKey>(key, secondsstamp));
    261      }
    262      mPersistCount = mKeys.Count();
    263      return NS_OK;
    264    }
    265 
    266    nsresult Write() {
    267      nsCOMPtr<nsIFile> file = GetFile();
    268      if (NS_WARN_IF(!file)) {
    269        return NS_ERROR_UNEXPECTED;
    270      }
    271 
    272      nsCOMPtr<nsIOutputStream> stream;
    273      nsresult rv =
    274          NS_NewSafeLocalFileOutputStream(getter_AddRefs(stream), file);
    275      if (NS_WARN_IF(NS_FAILED(rv))) {
    276        return rv;
    277      }
    278 
    279      nsAutoCString versionBuffer;
    280      versionBuffer.AppendLiteral(ORIGINKEYS_VERSION);
    281      versionBuffer.Append('\n');
    282 
    283      uint32_t count;
    284      rv = stream->Write(versionBuffer.Data(), versionBuffer.Length(), &count);
    285      if (NS_WARN_IF(NS_FAILED(rv))) {
    286        return rv;
    287      }
    288      if (count != versionBuffer.Length()) {
    289        return NS_ERROR_UNEXPECTED;
    290      }
    291      for (const auto& entry : mKeys) {
    292        const nsACString& origin = entry.GetKey();
    293        OriginKey* originKey = entry.GetWeak();
    294 
    295        if (!originKey->mSecondsStamp) {
    296          continue;  // don't write temporal ones
    297        }
    298 
    299        nsCString originBuffer;
    300        originBuffer.Append(originKey->mKey);
    301        originBuffer.Append(' ');
    302        originBuffer.AppendInt(originKey->mSecondsStamp);
    303        originBuffer.Append(' ');
    304        originBuffer.Append(origin);
    305        originBuffer.Append('\n');
    306 
    307        rv = stream->Write(originBuffer.Data(), originBuffer.Length(), &count);
    308        if (NS_WARN_IF(NS_FAILED(rv)) || count != originBuffer.Length()) {
    309          break;
    310        }
    311      }
    312 
    313      nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(stream);
    314      MOZ_ASSERT(safeStream);
    315 
    316      rv = safeStream->Finish();
    317      if (NS_WARN_IF(NS_FAILED(rv))) {
    318        return rv;
    319      }
    320      return NS_OK;
    321    }
    322 
    323    nsresult Load() {
    324      nsresult rv = Read();
    325      if (NS_WARN_IF(NS_FAILED(rv))) {
    326        Delete();
    327      }
    328      return rv;
    329    }
    330 
    331    nsresult Save() {
    332      nsresult rv = Write();
    333      if (NS_WARN_IF(NS_FAILED(rv))) {
    334        NS_WARNING("Failed to write data for EnumerateDevices id-persistence.");
    335        Delete();
    336      }
    337      return rv;
    338    }
    339 
    340    void Clear(int64_t aSinceWhen) {
    341      OriginKeysTable::Clear(aSinceWhen);
    342      Delete();
    343      Save();
    344    }
    345 
    346    nsresult Delete() {
    347      nsCOMPtr<nsIFile> file = GetFile();
    348      if (NS_WARN_IF(!file)) {
    349        return NS_ERROR_UNEXPECTED;
    350      }
    351      nsresult rv = file->Remove(false);
    352      if (rv == NS_ERROR_FILE_NOT_FOUND) {
    353        return NS_OK;
    354      }
    355      if (NS_WARN_IF(NS_FAILED(rv))) {
    356        return rv;
    357      }
    358      return NS_OK;
    359    }
    360 
    361    void SetProfileDir(nsIFile* aProfileDir) {
    362      MOZ_ASSERT(!NS_IsMainThread());
    363      bool first = !mProfileDir;
    364      mProfileDir = aProfileDir;
    365      // Load from disk when we first get a profileDir, but not subsequently.
    366      if (first) {
    367        Load();
    368      }
    369    }
    370 
    371   private:
    372    nsCOMPtr<nsIFile> mProfileDir;
    373  };
    374 
    375 private:
    376  static OriginKeyStore* sOriginKeyStore;
    377 
    378  virtual ~OriginKeyStore() {
    379    MOZ_ASSERT(NS_IsMainThread());
    380    sOriginKeyStore = nullptr;
    381    LOG(("%s", __FUNCTION__));
    382  }
    383 
    384 public:
    385  static RefPtr<OriginKeyStore> Get() {
    386    MOZ_ASSERT(NS_IsMainThread());
    387    if (!sOriginKeyStore) {
    388      sOriginKeyStore = new OriginKeyStore();
    389    }
    390    return RefPtr(sOriginKeyStore);
    391  }
    392 
    393  // Only accessed on StreamTS threads
    394  OriginKeysLoader mOriginKeys MOZ_GUARDED_BY(sOriginKeyStoreStsMutex);
    395  OriginKeysTable mPrivateBrowsingOriginKeys
    396      MOZ_GUARDED_BY(sOriginKeyStoreStsMutex);
    397 };
    398 OriginKeyStore* OriginKeyStore::sOriginKeyStore = nullptr;
    399 
    400 template <class Super>
    401 mozilla::ipc::IPCResult Parent<Super>::RecvGetPrincipalKey(
    402    const ipc::PrincipalInfo& aPrincipalInfo, const bool& aPersist,
    403    PMediaParent::GetPrincipalKeyResolver&& aResolve) {
    404  MOZ_ASSERT(NS_IsMainThread());
    405 
    406  // First, get profile dir.
    407 
    408  nsCOMPtr<nsIFile> profileDir;
    409  nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
    410                                       getter_AddRefs(profileDir));
    411  if (NS_WARN_IF(NS_FAILED(rv))) {
    412    return IPCResult(this, false);
    413  }
    414 
    415  // Resolver has to be called in MainThread but the key is discovered
    416  // in a different thread. We wrap the resolver around a MozPromise to make
    417  // it more flexible and pass it to the new task. When this is done the
    418  // resolver is resolved in MainThread.
    419 
    420  // Then over to stream-transport thread (a thread pool) to do the actual
    421  // file io. Stash a promise to hold the answer and get an id for this request.
    422 
    423  nsCOMPtr<nsIEventTarget> sts =
    424      do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
    425  MOZ_ASSERT(sts);
    426  auto taskQueue = TaskQueue::Create(sts.forget(), "RecvGetPrincipalKey");
    427  RefPtr<Parent<Super>> that(this);
    428 
    429  InvokeAsync(
    430      taskQueue, __func__,
    431      [this, that, profileDir, aPrincipalInfo, aPersist]() {
    432        MOZ_ASSERT(!NS_IsMainThread());
    433 
    434        StaticMutexAutoLock lock(sOriginKeyStoreStsMutex);
    435        mOriginKeyStore->mOriginKeys.SetProfileDir(profileDir);
    436 
    437        nsresult rv;
    438        nsAutoCString result;
    439        if (IsPrincipalInfoPrivate(aPrincipalInfo)) {
    440          rv = mOriginKeyStore->mPrivateBrowsingOriginKeys.GetPrincipalKey(
    441              aPrincipalInfo, result);
    442        } else {
    443          rv = mOriginKeyStore->mOriginKeys.GetPrincipalKey(aPrincipalInfo,
    444                                                            result, aPersist);
    445        }
    446 
    447        if (NS_WARN_IF(NS_FAILED(rv))) {
    448          return PrincipalKeyPromise::CreateAndReject(rv, __func__);
    449        }
    450        return PrincipalKeyPromise::CreateAndResolve(result, __func__);
    451      })
    452      ->Then(
    453          GetCurrentSerialEventTarget(), __func__,
    454          [aResolve](const PrincipalKeyPromise::ResolveOrRejectValue& aValue) {
    455            if (aValue.IsReject()) {
    456              aResolve(""_ns);
    457            } else {
    458              aResolve(aValue.ResolveValue());
    459            }
    460          });
    461 
    462  return IPC_OK();
    463 }
    464 
    465 template <class Super>
    466 mozilla::ipc::IPCResult Parent<Super>::RecvSanitizeOriginKeys(
    467    const uint64_t& aSinceWhen, const bool& aOnlyPrivateBrowsing) {
    468  MOZ_ASSERT(NS_IsMainThread());
    469  nsCOMPtr<nsIFile> profileDir;
    470  nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
    471                                       getter_AddRefs(profileDir));
    472  if (NS_WARN_IF(NS_FAILED(rv))) {
    473    return IPCResult(this, false);
    474  }
    475  // Over to stream-transport thread (a thread pool) to do the file io.
    476 
    477  nsCOMPtr<nsIEventTarget> sts =
    478      do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
    479  MOZ_ASSERT(sts);
    480  RefPtr<Parent<Super>> that(this);
    481 
    482  rv = sts->Dispatch(
    483      NewRunnableFrom(
    484          [this, that, profileDir, aSinceWhen, aOnlyPrivateBrowsing]() {
    485            MOZ_ASSERT(!NS_IsMainThread());
    486            StaticMutexAutoLock lock(sOriginKeyStoreStsMutex);
    487            mOriginKeyStore->mPrivateBrowsingOriginKeys.Clear(aSinceWhen);
    488            if (!aOnlyPrivateBrowsing) {
    489              mOriginKeyStore->mOriginKeys.SetProfileDir(profileDir);
    490              mOriginKeyStore->mOriginKeys.Clear(aSinceWhen);
    491            }
    492            return NS_OK;
    493          }),
    494      NS_DISPATCH_NORMAL);
    495  if (NS_WARN_IF(NS_FAILED(rv))) {
    496    return IPCResult(this, false);
    497  }
    498  return IPC_OK();
    499 }
    500 
    501 template <class Super>
    502 void Parent<Super>::ActorDestroy(ActorDestroyReason aWhy) {
    503  // No more IPC from here
    504  mDestroyed = true;
    505  LOG(("%s", __FUNCTION__));
    506 }
    507 
    508 template <class Super>
    509 Parent<Super>::Parent()
    510    : mOriginKeyStore(OriginKeyStore::Get()), mDestroyed(false) {
    511  LOG(("media::Parent: %p", this));
    512 }
    513 
    514 template <class Super>
    515 Parent<Super>::~Parent() {
    516  LOG(("~media::Parent: %p", this));
    517 }
    518 
    519 PMediaParent* AllocPMediaParent() {
    520  Parent<PMediaParent>* obj = new Parent<PMediaParent>();
    521  obj->AddRef();
    522  return obj;
    523 }
    524 
    525 bool DeallocPMediaParent(media::PMediaParent* aActor) {
    526  static_cast<Parent<PMediaParent>*>(aActor)->Release();
    527  return true;
    528 }
    529 
    530 }  // namespace mozilla::media
    531 
    532 #undef LOG
    533 
    534 // Instantiate templates to satisfy linker
    535 template class mozilla::media::Parent<mozilla::media::NonE10s>;