tor-browser

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

ChromiumCDMAdapter.cpp (10316B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "ChromiumCDMAdapter.h"
      7 
      8 #include <utility>
      9 
     10 #include "GMPLog.h"
     11 #include "WidevineUtils.h"
     12 #include "content_decryption_module.h"
     13 #include "content_decryption_module_ext.h"
     14 #include "gmp-api/gmp-entrypoints.h"
     15 #include "gmp-api/gmp-video-codec.h"
     16 #include "mozilla/HelperMacros.h"
     17 #include "mozilla/PodOperations.h"
     18 #include "mozilla/dom/KeySystemNames.h"
     19 
     20 #ifdef XP_WIN
     21 #  include <strsafe.h>
     22 #  include <windows.h>
     23 
     24 #  include <unordered_map>
     25 #  include <vector>
     26 
     27 #  include "WinUtils.h"
     28 #  include "nsWindowsDllInterceptor.h"
     29 #else
     30 #  include <fcntl.h>
     31 #  include <sys/stat.h>
     32 #  include <sys/types.h>
     33 #  include <unistd.h>
     34 #endif
     35 
     36 const GMPPlatformAPI* sPlatform = nullptr;
     37 
     38 namespace mozilla {
     39 
     40 #ifdef XP_WIN
     41 static void InitializeHooks();
     42 #endif
     43 
     44 ChromiumCDMAdapter::ChromiumCDMAdapter(
     45    nsTArray<std::pair<nsCString, nsCString>>&& aHostPathPairs) {
     46 #ifdef XP_WIN
     47  InitializeHooks();
     48 #endif
     49  PopulateHostFiles(std::move(aHostPathPairs));
     50 }
     51 
     52 void ChromiumCDMAdapter::SetAdaptee(PRLibrary* aLib) { mLib = aLib; }
     53 
     54 void* ChromiumCdmHost(int aHostInterfaceVersion, void* aUserData) {
     55  GMP_LOG_DEBUG("ChromiumCdmHostFunc(%d, %p)", aHostInterfaceVersion,
     56                aUserData);
     57  if (aHostInterfaceVersion != cdm::Host_11::kVersion) {
     58    return nullptr;
     59  }
     60  return aUserData;
     61 }
     62 
     63 void* ChromiumCdmHostCompat(int aHostInterfaceVersion, void* aUserData) {
     64  GMP_LOG_DEBUG("ChromiumCdmHostCompatFunc(%d, %p)", aHostInterfaceVersion,
     65                aUserData);
     66  if (aHostInterfaceVersion != cdm::Host_10::kVersion) {
     67    return nullptr;
     68  }
     69  return aUserData;
     70 }
     71 
     72 #ifdef MOZILLA_OFFICIAL
     73 static cdm::HostFile TakeToCDMHostFile(HostFileData& aHostFileData) {
     74  return cdm::HostFile(aHostFileData.mBinary.Path().get(),
     75                       aHostFileData.mBinary.TakePlatformFile(),
     76                       aHostFileData.mSig.TakePlatformFile());
     77 }
     78 #endif
     79 
     80 GMPErr ChromiumCDMAdapter::GMPInit(const GMPPlatformAPI* aPlatformAPI) {
     81  GMP_LOG_DEBUG("ChromiumCDMAdapter::GMPInit");
     82  sPlatform = aPlatformAPI;
     83  if (NS_WARN_IF(!mLib)) {
     84    MOZ_CRASH("Missing library!");
     85    return GMPGenericErr;
     86  }
     87 
     88 #ifdef MOZILLA_OFFICIAL
     89  // Note: we must call the VerifyCdmHost_0 function if it's present before
     90  // we call the initialize function.
     91  auto verify = reinterpret_cast<decltype(::VerifyCdmHost_0)*>(
     92      PR_FindFunctionSymbol(mLib, MOZ_STRINGIFY(VerifyCdmHost_0)));
     93  if (verify) {
     94    nsTArray<cdm::HostFile> files;
     95    for (HostFileData& hostFile : mHostFiles) {
     96      files.AppendElement(TakeToCDMHostFile(hostFile));
     97    }
     98    bool result = verify(files.Elements(), files.Length());
     99    GMP_LOG_DEBUG("%s VerifyCdmHost_0 returned %d", __func__, result);
    100    MOZ_DIAGNOSTIC_ASSERT(result, "Verification failed!");
    101  }
    102 #endif
    103 
    104  auto init = reinterpret_cast<decltype(::INITIALIZE_CDM_MODULE)*>(
    105      PR_FindFunctionSymbol(mLib, MOZ_STRINGIFY(INITIALIZE_CDM_MODULE)));
    106  if (!init) {
    107    MOZ_CRASH("Missing init method!");
    108    return GMPGenericErr;
    109  }
    110 
    111  GMP_LOG_DEBUG(MOZ_STRINGIFY(INITIALIZE_CDM_MODULE) "()");
    112  init();
    113 
    114  return GMPNoErr;
    115 }
    116 
    117 GMPErr ChromiumCDMAdapter::GMPGetAPI(const char* aAPIName, void* aHostAPI,
    118                                     void** aPluginAPI,
    119                                     const nsACString& aKeySystem) {
    120  MOZ_ASSERT(
    121      aKeySystem.EqualsLiteral(kWidevineKeySystemName) ||
    122          aKeySystem.EqualsLiteral(kClearKeyKeySystemName) ||
    123          aKeySystem.EqualsLiteral(kClearKeyWithProtectionQueryKeySystemName) ||
    124          aKeySystem.EqualsLiteral("fake"),
    125      "Should not get an unrecognized key system. Why didn't it get "
    126      "blocked by MediaKeySystemAccess?");
    127  GMP_LOG_DEBUG("ChromiumCDMAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %s) this=0x%p",
    128                aAPIName, aHostAPI, aPluginAPI,
    129                PromiseFlatCString(aKeySystem).get(), this);
    130 
    131  int version;
    132  GetCdmHostFunc getCdmHostFunc;
    133  if (!strcmp(aAPIName, CHROMIUM_CDM_API)) {
    134    version = cdm::ContentDecryptionModule_11::kVersion;
    135    getCdmHostFunc = &ChromiumCdmHost;
    136  } else if (!strcmp(aAPIName, CHROMIUM_CDM_API_BACKWARD_COMPAT)) {
    137    version = cdm::ContentDecryptionModule_10::kVersion;
    138    getCdmHostFunc = &ChromiumCdmHostCompat;
    139  } else {
    140    MOZ_ASSERT_UNREACHABLE("We only support and expect cdm10/11!");
    141    GMP_LOG_DEBUG(
    142        "ChromiumCDMAdapter::GMPGetAPI(%s, 0x%p, 0x%p) this=0x%p got "
    143        "unsupported CDM version!",
    144        aAPIName, aHostAPI, aPluginAPI, this);
    145    return GMPGenericErr;
    146  }
    147 
    148  auto create = reinterpret_cast<decltype(::CreateCdmInstance)*>(
    149      PR_FindFunctionSymbol(mLib, "CreateCdmInstance"));
    150  if (!create) {
    151    GMP_LOG_DEBUG(
    152        "ChromiumCDMAdapter::GMPGetAPI(%s, 0x%p, 0x%p) this=0x%p "
    153        "FAILED to find CreateCdmInstance",
    154        aAPIName, aHostAPI, aPluginAPI, this);
    155    return GMPGenericErr;
    156  }
    157 
    158  void* cdm = create(version, aKeySystem.BeginReading(), aKeySystem.Length(),
    159                     getCdmHostFunc, aHostAPI);
    160  if (!cdm) {
    161    GMP_LOG_DEBUG(
    162        "ChromiumCDMAdapter::GMPGetAPI(%s, 0x%p, 0x%p) this=0x%p "
    163        "FAILED to create cdm version %d",
    164        aAPIName, aHostAPI, aPluginAPI, this, version);
    165    return GMPGenericErr;
    166  }
    167  GMP_LOG_DEBUG("cdm: 0x%p, version: %d", cdm, version);
    168  *aPluginAPI = cdm;
    169 
    170  return *aPluginAPI ? GMPNoErr : GMPNotImplementedErr;
    171 }
    172 
    173 void ChromiumCDMAdapter::GMPShutdown() {
    174  GMP_LOG_DEBUG("ChromiumCDMAdapter::GMPShutdown()");
    175 
    176  decltype(::DeinitializeCdmModule)* deinit;
    177  deinit =
    178      (decltype(deinit))(PR_FindFunctionSymbol(mLib, "DeinitializeCdmModule"));
    179  if (deinit) {
    180    GMP_LOG_DEBUG("DeinitializeCdmModule()");
    181    deinit();
    182  }
    183 }
    184 
    185 /* static */
    186 bool ChromiumCDMAdapter::Supports(int32_t aModuleVersion,
    187                                  int32_t aInterfaceVersion,
    188                                  int32_t aHostVersion) {
    189  return aModuleVersion == CDM_MODULE_VERSION &&
    190         ((aInterfaceVersion == cdm::ContentDecryptionModule_11::kVersion &&
    191           aHostVersion == cdm::Host_11::kVersion) ||
    192          (aInterfaceVersion == cdm::ContentDecryptionModule_10::kVersion &&
    193           aHostVersion == cdm::Host_10::kVersion));
    194 }
    195 
    196 #ifdef XP_WIN
    197 
    198 MOZ_RUNINIT static WindowsDllInterceptor sKernel32Intercept;
    199 
    200 typedef DWORD(WINAPI* QueryDosDeviceWFnPtr)(_In_opt_ LPCWSTR lpDeviceName,
    201                                            _Out_ LPWSTR lpTargetPath,
    202                                            _In_ DWORD ucchMax);
    203 
    204 static WindowsDllInterceptor::FuncHookType<QueryDosDeviceWFnPtr>
    205    sOriginalQueryDosDeviceWFnPtr;
    206 
    207 static std::unordered_map<std::wstring, std::wstring>* sDeviceNames = nullptr;
    208 
    209 DWORD WINAPI QueryDosDeviceWHook(LPCWSTR lpDeviceName, LPWSTR lpTargetPath,
    210                                 DWORD ucchMax) {
    211  if (!sDeviceNames) {
    212    return 0;
    213  }
    214  std::wstring name = std::wstring(lpDeviceName);
    215  auto iter = sDeviceNames->find(name);
    216  if (iter == sDeviceNames->end()) {
    217    return 0;
    218  }
    219  const std::wstring& device = iter->second;
    220  if (device.size() + 1 > ucchMax) {
    221    return 0;
    222  }
    223  PodCopy(lpTargetPath, device.c_str(), device.size());
    224  lpTargetPath[device.size()] = 0;
    225  GMP_LOG_DEBUG("QueryDosDeviceWHook %S -> %S", lpDeviceName, lpTargetPath);
    226  return device.size();
    227 }
    228 
    229 static std::vector<std::wstring> GetDosDeviceNames() {
    230  std::vector<std::wstring> v;
    231  std::vector<wchar_t> buf;
    232  buf.resize(1024);
    233  DWORD rv = GetLogicalDriveStringsW(buf.size(), buf.data());
    234  if (rv == 0 || rv > buf.size()) {
    235    return v;
    236  }
    237 
    238  // buf will be a list of null terminated strings, with the last string
    239  // being 0 length.
    240  const wchar_t* p = buf.data();
    241  const wchar_t* end = &buf.back();
    242  size_t l;
    243  while (p < end && (l = wcsnlen_s(p, end - p)) > 0) {
    244    // The string is of the form "C:\". We need to strip off the trailing
    245    // backslash.
    246    std::wstring drive = std::wstring(p, p + l);
    247    if (drive.back() == '\\') {
    248      drive.erase(drive.end() - 1);
    249    }
    250    v.push_back(std::move(drive));
    251    p += l + 1;
    252  }
    253  return v;
    254 }
    255 
    256 static std::wstring GetDeviceMapping(const std::wstring& aDosDeviceName) {
    257  wchar_t buf[MAX_PATH] = {0};
    258  DWORD rv = QueryDosDeviceW(aDosDeviceName.c_str(), buf, MAX_PATH);
    259  if (rv == 0) {
    260    return std::wstring(L"");
    261  }
    262  return std::wstring(buf, buf + rv);
    263 }
    264 
    265 static void InitializeHooks() {
    266  static bool initialized = false;
    267  if (initialized) {
    268    return;
    269  }
    270  initialized = true;
    271  sDeviceNames = new std::unordered_map<std::wstring, std::wstring>();
    272  for (const std::wstring& name : GetDosDeviceNames()) {
    273    sDeviceNames->emplace(name, GetDeviceMapping(name));
    274  }
    275 
    276  sKernel32Intercept.Init("kernelbase.dll");
    277  sOriginalQueryDosDeviceWFnPtr.Set(sKernel32Intercept, "QueryDosDeviceW",
    278                                    &QueryDosDeviceWHook);
    279 }
    280 #endif
    281 
    282 HostFile::HostFile(HostFile&& aOther)
    283    : mPath(aOther.mPath), mFile(aOther.TakePlatformFile()) {}
    284 
    285 HostFile::~HostFile() {
    286  if (mFile != cdm::kInvalidPlatformFile) {
    287 #ifdef XP_WIN
    288    CloseHandle(mFile);
    289 #else
    290    close(mFile);
    291 #endif
    292    mFile = cdm::kInvalidPlatformFile;
    293  }
    294 }
    295 
    296 #ifdef XP_WIN
    297 HostFile::HostFile(const nsCString& aPath)
    298    : mPath(NS_ConvertUTF8toUTF16(aPath)) {
    299  HANDLE handle =
    300      CreateFileW(mPath.get(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
    301                  NULL, OPEN_EXISTING, 0, NULL);
    302  mFile = (handle == INVALID_HANDLE_VALUE) ? cdm::kInvalidPlatformFile : handle;
    303 }
    304 #endif
    305 
    306 #ifndef XP_WIN
    307 HostFile::HostFile(const nsCString& aPath) : mPath(aPath) {
    308  // Note: open() returns -1 on failure; i.e. kInvalidPlatformFile.
    309  mFile = open(aPath.get(), O_RDONLY);
    310 }
    311 #endif
    312 
    313 cdm::PlatformFile HostFile::TakePlatformFile() {
    314  cdm::PlatformFile f = mFile;
    315  mFile = cdm::kInvalidPlatformFile;
    316  return f;
    317 }
    318 
    319 void ChromiumCDMAdapter::PopulateHostFiles(
    320    nsTArray<std::pair<nsCString, nsCString>>&& aHostPathPairs) {
    321  for (const auto& pair : aHostPathPairs) {
    322    mHostFiles.AppendElement(HostFileData(mozilla::HostFile(pair.first),
    323                                          mozilla::HostFile(pair.second)));
    324  }
    325 }
    326 
    327 }  // namespace mozilla