Home | History | Annotate | Download | only in pnacl
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/component_updater/pnacl/pnacl_component_installer.h"
      6 
      7 #include <stdint.h>
      8 #include <string>
      9 #include <vector>
     10 
     11 #include "base/atomicops.h"
     12 #include "base/base_paths.h"
     13 #include "base/bind.h"
     14 #include "base/callback.h"
     15 #include "base/compiler_specific.h"
     16 #include "base/files/file_enumerator.h"
     17 #include "base/files/file_path.h"
     18 #include "base/files/file_util.h"
     19 #include "base/json/json_file_value_serializer.h"
     20 #include "base/logging.h"
     21 #include "base/path_service.h"
     22 #include "base/strings/string_util.h"
     23 #include "base/values.h"
     24 #include "base/version.h"
     25 #include "base/win/windows_version.h"
     26 #include "build/build_config.h"
     27 #include "chrome/browser/browser_process.h"
     28 #include "chrome/common/chrome_paths.h"
     29 #include "components/component_updater/component_updater_service.h"
     30 #include "components/nacl/common/nacl_switches.h"
     31 #include "components/omaha_query_params/omaha_query_params.h"
     32 #include "content/public/browser/browser_thread.h"
     33 
     34 using content::BrowserThread;
     35 using omaha_query_params::OmahaQueryParams;
     36 
     37 namespace component_updater {
     38 
     39 namespace {
     40 
     41 // Name of the Pnacl component specified in the manifest.
     42 const char kPnaclManifestName[] = "PNaCl Translator";
     43 
     44 // Sanitize characters from Pnacl Arch value so that they can be used
     45 // in path names.  This should only be characters in the set: [a-z0-9_].
     46 // Keep in sync with chrome/browser/nacl_host/nacl_file_host.
     47 std::string SanitizeForPath(const std::string& input) {
     48   std::string result;
     49   base::ReplaceChars(input, "-", "_", &result);
     50   return result;
     51 }
     52 
     53 // Set the component's hash to the multi-CRX PNaCl package.
     54 void SetPnaclHash(CrxComponent* component) {
     55   static const uint8_t sha256_hash[32] = {
     56       // This corresponds to AppID: hnimpnehoodheedghdeeijklkeaacbdc
     57       0x7d, 0x8c, 0xfd, 0x47, 0xee, 0x37, 0x44, 0x36,
     58       0x73, 0x44, 0x89, 0xab, 0xa4, 0x00, 0x21, 0x32,
     59       0x4a, 0x06, 0x06, 0xf1, 0x51, 0x3c, 0x51, 0xba,
     60       0x31, 0x2f, 0xbc, 0xb3, 0x99, 0x07, 0xdc, 0x9c
     61   };
     62 
     63   component->pk_hash.assign(sha256_hash, &sha256_hash[arraysize(sha256_hash)]);
     64 }
     65 
     66 // If we don't have Pnacl installed, this is the version we claim.
     67 const char kNullVersion[] = "0.0.0.0";
     68 const char kMinPnaclVersion[] = "0.1.0.13367";
     69 
     70 // Initially say that we do not need OnDemand updates. This should be
     71 // updated by CheckVersionCompatiblity(), before doing any URLRequests
     72 // that depend on PNaCl.
     73 volatile base::subtle::Atomic32 needs_on_demand_update = 0;
     74 
     75 void CheckVersionCompatiblity(const base::Version& current_version) {
     76   // Using NoBarrier, since needs_on_demand_update is standalone and does
     77   // not have other associated data.
     78   base::subtle::NoBarrier_Store(&needs_on_demand_update,
     79                                 current_version.IsOlderThan(kMinPnaclVersion));
     80 }
     81 
     82 // PNaCl is packaged as a multi-CRX.  This returns the platform-specific
     83 // subdirectory that is part of that multi-CRX.
     84 base::FilePath GetPlatformDir(const base::FilePath& base_path) {
     85   std::string arch = SanitizeForPath(OmahaQueryParams::GetNaclArch());
     86   return base_path.AppendASCII("_platform_specific").AppendASCII(arch);
     87 }
     88 
     89 // Tell the rest of the world where to find the platform-specific PNaCl files.
     90 void OverrideDirPnaclComponent(const base::FilePath& base_path) {
     91   PathService::Override(chrome::DIR_PNACL_COMPONENT, GetPlatformDir(base_path));
     92 }
     93 
     94 bool GetLatestPnaclDirectory(PnaclComponentInstaller* pci,
     95                              base::FilePath* latest_dir,
     96                              Version* latest_version,
     97                              std::vector<base::FilePath>* older_dirs) {
     98   // Enumerate all versions starting from the base directory.
     99   base::FilePath base_dir = pci->GetPnaclBaseDirectory();
    100   bool found = false;
    101   base::FileEnumerator file_enumerator(
    102       base_dir, false, base::FileEnumerator::DIRECTORIES);
    103   for (base::FilePath path = file_enumerator.Next(); !path.value().empty();
    104        path = file_enumerator.Next()) {
    105     Version version(path.BaseName().MaybeAsASCII());
    106     if (!version.IsValid())
    107       continue;
    108     if (found) {
    109       if (version.CompareTo(*latest_version) > 0) {
    110         older_dirs->push_back(*latest_dir);
    111         *latest_dir = path;
    112         *latest_version = version;
    113       } else {
    114         older_dirs->push_back(path);
    115       }
    116     } else {
    117       *latest_version = version;
    118       *latest_dir = path;
    119       found = true;
    120     }
    121   }
    122   return found;
    123 }
    124 
    125 // Read a manifest file in.
    126 base::DictionaryValue* ReadJSONManifest(const base::FilePath& manifest_path) {
    127   JSONFileValueSerializer serializer(manifest_path);
    128   std::string error;
    129   scoped_ptr<base::Value> root(serializer.Deserialize(NULL, &error));
    130   if (!root.get())
    131     return NULL;
    132   if (!root->IsType(base::Value::TYPE_DICTIONARY))
    133     return NULL;
    134   return static_cast<base::DictionaryValue*>(root.release());
    135 }
    136 
    137 // Read the PNaCl specific manifest.
    138 base::DictionaryValue* ReadPnaclManifest(const base::FilePath& unpack_path) {
    139   base::FilePath manifest_path =
    140       GetPlatformDir(unpack_path).AppendASCII("pnacl_public_pnacl_json");
    141   if (!base::PathExists(manifest_path))
    142     return NULL;
    143   return ReadJSONManifest(manifest_path);
    144 }
    145 
    146 // Read the component's manifest.json.
    147 base::DictionaryValue* ReadComponentManifest(
    148     const base::FilePath& unpack_path) {
    149   base::FilePath manifest_path =
    150       unpack_path.Append(FILE_PATH_LITERAL("manifest.json"));
    151   if (!base::PathExists(manifest_path))
    152     return NULL;
    153   return ReadJSONManifest(manifest_path);
    154 }
    155 
    156 // Check that the component's manifest is for PNaCl, and check the
    157 // PNaCl manifest indicates this is the correct arch-specific package.
    158 bool CheckPnaclComponentManifest(const base::DictionaryValue& manifest,
    159                                  const base::DictionaryValue& pnacl_manifest,
    160                                  Version* version_out) {
    161   // Make sure we have the right |manifest| file.
    162   std::string name;
    163   if (!manifest.GetStringASCII("name", &name)) {
    164     LOG(WARNING) << "'name' field is missing from manifest!";
    165     return false;
    166   }
    167   // For the webstore, we've given different names to each of the
    168   // architecture specific packages (and test/QA vs not test/QA)
    169   // so only part of it is the same.
    170   if (name.find(kPnaclManifestName) == std::string::npos) {
    171     LOG(WARNING) << "'name' field in manifest is invalid (" << name
    172                  << ") -- missing (" << kPnaclManifestName << ")";
    173     return false;
    174   }
    175 
    176   std::string proposed_version;
    177   if (!manifest.GetStringASCII("version", &proposed_version)) {
    178     LOG(WARNING) << "'version' field is missing from manifest!";
    179     return false;
    180   }
    181   Version version(proposed_version.c_str());
    182   if (!version.IsValid()) {
    183     LOG(WARNING) << "'version' field in manifest is invalid "
    184                  << version.GetString();
    185     return false;
    186   }
    187 
    188   // Now check the |pnacl_manifest|.
    189   std::string arch;
    190   if (!pnacl_manifest.GetStringASCII("pnacl-arch", &arch)) {
    191     LOG(WARNING) << "'pnacl-arch' field is missing from pnacl-manifest!";
    192     return false;
    193   }
    194   if (arch.compare(OmahaQueryParams::GetNaclArch()) != 0) {
    195     LOG(WARNING) << "'pnacl-arch' field in manifest is invalid (" << arch
    196                  << " vs " << OmahaQueryParams::GetNaclArch() << ")";
    197     return false;
    198   }
    199 
    200   *version_out = version;
    201   return true;
    202 }
    203 
    204 }  // namespace
    205 
    206 PnaclComponentInstaller::PnaclComponentInstaller() : cus_(NULL) {
    207 }
    208 
    209 PnaclComponentInstaller::~PnaclComponentInstaller() {
    210 }
    211 
    212 void PnaclComponentInstaller::OnUpdateError(int error) {
    213   NOTREACHED() << "Pnacl update error: " << error;
    214 }
    215 
    216 // Pnacl components have the version encoded in the path itself:
    217 // <profile>\AppData\Local\Google\Chrome\User Data\pnacl\0.1.2.3\.
    218 // and the base directory will be:
    219 // <profile>\AppData\Local\Google\Chrome\User Data\pnacl\.
    220 base::FilePath PnaclComponentInstaller::GetPnaclBaseDirectory() {
    221   base::FilePath result;
    222   CHECK(PathService::Get(chrome::DIR_PNACL_BASE, &result));
    223   return result;
    224 }
    225 
    226 bool PnaclComponentInstaller::Install(const base::DictionaryValue& manifest,
    227                                       const base::FilePath& unpack_path) {
    228   scoped_ptr<base::DictionaryValue> pnacl_manifest(
    229       ReadPnaclManifest(unpack_path));
    230   if (pnacl_manifest == NULL) {
    231     LOG(WARNING) << "Failed to read pnacl manifest.";
    232     return false;
    233   }
    234 
    235   Version version;
    236   if (!CheckPnaclComponentManifest(manifest, *pnacl_manifest, &version)) {
    237     LOG(WARNING) << "CheckPnaclComponentManifest failed, not installing.";
    238     return false;
    239   }
    240 
    241   // Don't install if the current version is actually newer.
    242   if (current_version().CompareTo(version) > 0) {
    243     return false;
    244   }
    245 
    246   // Passed the basic tests. Time to install it.
    247   base::FilePath path =
    248       GetPnaclBaseDirectory().AppendASCII(version.GetString());
    249   if (base::PathExists(path)) {
    250     if (!base::DeleteFile(path, true))
    251       return false;
    252   }
    253   if (!base::Move(unpack_path, path)) {
    254     LOG(WARNING) << "Move failed, not installing.";
    255     return false;
    256   }
    257 
    258   // Installation is done. Now tell the rest of chrome.
    259   // - The path service.
    260   // - Callbacks that requested an update.
    261   set_current_version(version);
    262   CheckVersionCompatiblity(version);
    263   OverrideDirPnaclComponent(path);
    264   return true;
    265 }
    266 
    267 // Given |file|, which can be a path like "_platform_specific/arm/pnacl_foo",
    268 // returns the assumed install path. The path separator in |file| is '/'
    269 // for all platforms. Caller is responsible for checking that the
    270 // |installed_file| actually exists.
    271 bool PnaclComponentInstaller::GetInstalledFile(const std::string& file,
    272                                                base::FilePath* installed_file) {
    273   if (current_version().Equals(Version(kNullVersion)))
    274     return false;
    275 
    276   *installed_file = GetPnaclBaseDirectory()
    277                         .AppendASCII(current_version().GetString())
    278                         .AppendASCII(file);
    279   return true;
    280 }
    281 
    282 CrxComponent PnaclComponentInstaller::GetCrxComponent() {
    283   CrxComponent pnacl_component;
    284   pnacl_component.version = current_version();
    285   pnacl_component.name = "pnacl";
    286   pnacl_component.installer = this;
    287   pnacl_component.fingerprint = current_fingerprint();
    288   SetPnaclHash(&pnacl_component);
    289 
    290   return pnacl_component;
    291 }
    292 
    293 namespace {
    294 
    295 void FinishPnaclUpdateRegistration(const Version& current_version,
    296                                    const std::string& current_fingerprint,
    297                                    PnaclComponentInstaller* pci) {
    298   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    299   pci->set_current_version(current_version);
    300   CheckVersionCompatiblity(current_version);
    301   pci->set_current_fingerprint(current_fingerprint);
    302   CrxComponent pnacl_component = pci->GetCrxComponent();
    303 
    304   ComponentUpdateService::Status status =
    305       pci->cus()->RegisterComponent(pnacl_component);
    306   if (status != ComponentUpdateService::kOk &&
    307       status != ComponentUpdateService::kReplaced) {
    308     NOTREACHED() << "Pnacl component registration failed.";
    309   }
    310 }
    311 
    312 // Check if there is an existing version on disk first to know when
    313 // a hosted version is actually newer.
    314 void StartPnaclUpdateRegistration(PnaclComponentInstaller* pci) {
    315   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    316   base::FilePath path = pci->GetPnaclBaseDirectory();
    317   if (!base::PathExists(path)) {
    318     if (!base::CreateDirectory(path)) {
    319       NOTREACHED() << "Could not create base Pnacl directory.";
    320       return;
    321     }
    322   }
    323 
    324   Version current_version(kNullVersion);
    325   std::string current_fingerprint;
    326   std::vector<base::FilePath> older_dirs;
    327   if (GetLatestPnaclDirectory(pci, &path, &current_version, &older_dirs)) {
    328     scoped_ptr<base::DictionaryValue> manifest(ReadComponentManifest(path));
    329     scoped_ptr<base::DictionaryValue> pnacl_manifest(ReadPnaclManifest(path));
    330     Version manifest_version;
    331     // Check that the component manifest and PNaCl manifest files
    332     // are legit, and that the indicated version matches the one
    333     // encoded within the path name.
    334     if (manifest == NULL || pnacl_manifest == NULL ||
    335         !CheckPnaclComponentManifest(*manifest,
    336                                      *pnacl_manifest,
    337                                      &manifest_version) ||
    338         !current_version.Equals(manifest_version)) {
    339       current_version = Version(kNullVersion);
    340     } else {
    341       OverrideDirPnaclComponent(path);
    342       base::ReadFileToString(path.AppendASCII("manifest.fingerprint"),
    343                              &current_fingerprint);
    344     }
    345   }
    346 
    347   BrowserThread::PostTask(BrowserThread::UI,
    348                           FROM_HERE,
    349                           base::Bind(&FinishPnaclUpdateRegistration,
    350                                      current_version,
    351                                      current_fingerprint,
    352                                      pci));
    353 
    354   // Remove older versions of PNaCl.
    355   for (std::vector<base::FilePath>::iterator iter = older_dirs.begin();
    356        iter != older_dirs.end();
    357        ++iter) {
    358     base::DeleteFile(*iter, true);
    359   }
    360 }
    361 
    362 }  // namespace
    363 
    364 void PnaclComponentInstaller::RegisterPnaclComponent(
    365     ComponentUpdateService* cus) {
    366   cus_ = cus;
    367   BrowserThread::PostTask(BrowserThread::FILE,
    368                           FROM_HERE,
    369                           base::Bind(&StartPnaclUpdateRegistration, this));
    370 }
    371 
    372 }  // namespace component_updater
    373 
    374 namespace pnacl {
    375 
    376 bool NeedsOnDemandUpdate() {
    377   return base::subtle::NoBarrier_Load(
    378              &component_updater::needs_on_demand_update) != 0;
    379 }
    380 
    381 }  // namespace pnacl
    382