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>;