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 <string>
      8 #include <vector>
      9 
     10 #include "base/atomicops.h"
     11 #include "base/base_paths.h"
     12 #include "base/bind.h"
     13 #include "base/callback.h"
     14 #include "base/command_line.h"
     15 #include "base/compiler_specific.h"
     16 #include "base/file_util.h"
     17 #include "base/files/file_enumerator.h"
     18 #include "base/files/file_path.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/browser/component_updater/component_updater_service.h"
     29 #include "chrome/browser/omaha_query_params/omaha_query_params.h"
     30 #include "chrome/common/chrome_paths.h"
     31 #include "components/nacl/common/nacl_switches.h"
     32 #include "content/public/browser/browser_thread.h"
     33 
     34 using chrome::OmahaQueryParams;
     35 using content::BrowserThread;
     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 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()
    207     : updates_disabled_(false), cus_(NULL) {
    208 }
    209 
    210 PnaclComponentInstaller::~PnaclComponentInstaller() {
    211 }
    212 
    213 void PnaclComponentInstaller::OnUpdateError(int error) {
    214   NOTREACHED() << "Pnacl update error: " << error;
    215 }
    216 
    217 // Pnacl components have the version encoded in the path itself:
    218 // <profile>\AppData\Local\Google\Chrome\User Data\pnacl\0.1.2.3\.
    219 // and the base directory will be:
    220 // <profile>\AppData\Local\Google\Chrome\User Data\pnacl\.
    221 base::FilePath PnaclComponentInstaller::GetPnaclBaseDirectory() {
    222   base::FilePath result;
    223   CHECK(PathService::Get(chrome::DIR_PNACL_BASE, &result));
    224   return result;
    225 }
    226 
    227 bool PnaclComponentInstaller::Install(const base::DictionaryValue& manifest,
    228                                       const base::FilePath& unpack_path) {
    229   scoped_ptr<base::DictionaryValue> pnacl_manifest(
    230       ReadPnaclManifest(unpack_path));
    231   if (pnacl_manifest == NULL) {
    232     LOG(WARNING) << "Failed to read pnacl manifest.";
    233     return false;
    234   }
    235 
    236   Version version;
    237   if (!CheckPnaclComponentManifest(manifest, *pnacl_manifest, &version)) {
    238     LOG(WARNING) << "CheckPnaclComponentManifest failed, not installing.";
    239     return false;
    240   }
    241 
    242   // Don't install if the current version is actually newer.
    243   if (current_version().CompareTo(version) > 0) {
    244     return false;
    245   }
    246 
    247   // Passed the basic tests. Time to install it.
    248   base::FilePath path =
    249       GetPnaclBaseDirectory().AppendASCII(version.GetString());
    250   if (base::PathExists(path)) {
    251     if (!base::DeleteFile(path, true))
    252       return false;
    253   }
    254   if (!base::Move(unpack_path, path)) {
    255     LOG(WARNING) << "Move failed, not installing.";
    256     return false;
    257   }
    258 
    259   // Installation is done. Now tell the rest of chrome.
    260   // - The path service.
    261   // - Callbacks that requested an update.
    262   set_current_version(version);
    263   CheckVersionCompatiblity(version);
    264   OverrideDirPnaclComponent(path);
    265   return true;
    266 }
    267 
    268 // Given |file|, which can be a path like "_platform_specific/arm/pnacl_foo",
    269 // returns the assumed install path. The path separator in |file| is '/'
    270 // for all platforms. Caller is responsible for checking that the
    271 // |installed_file| actually exists.
    272 bool PnaclComponentInstaller::GetInstalledFile(const std::string& file,
    273                                                base::FilePath* installed_file) {
    274   if (current_version().Equals(Version(kNullVersion)))
    275     return false;
    276 
    277   *installed_file = GetPnaclBaseDirectory()
    278                         .AppendASCII(current_version().GetString())
    279                         .AppendASCII(file);
    280   return true;
    281 }
    282 
    283 CrxComponent PnaclComponentInstaller::GetCrxComponent() {
    284   CrxComponent pnacl_component;
    285   pnacl_component.version = current_version();
    286   pnacl_component.name = "pnacl";
    287   pnacl_component.installer = this;
    288   pnacl_component.fingerprint = current_fingerprint();
    289   SetPnaclHash(&pnacl_component);
    290 
    291   return pnacl_component;
    292 }
    293 
    294 namespace {
    295 
    296 void FinishPnaclUpdateRegistration(const Version& current_version,
    297                                    const std::string& current_fingerprint,
    298                                    PnaclComponentInstaller* pci) {
    299   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    300   pci->set_current_version(current_version);
    301   CheckVersionCompatiblity(current_version);
    302   pci->set_current_fingerprint(current_fingerprint);
    303   CrxComponent pnacl_component = pci->GetCrxComponent();
    304 
    305   ComponentUpdateService::Status status =
    306       pci->cus()->RegisterComponent(pnacl_component);
    307   if (status != ComponentUpdateService::kOk &&
    308       status != ComponentUpdateService::kReplaced) {
    309     NOTREACHED() << "Pnacl component registration failed.";
    310   }
    311 }
    312 
    313 // Check if there is an existing version on disk first to know when
    314 // a hosted version is actually newer.
    315 void StartPnaclUpdateRegistration(PnaclComponentInstaller* pci) {
    316   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    317   base::FilePath path = pci->GetPnaclBaseDirectory();
    318   if (!base::PathExists(path)) {
    319     if (!base::CreateDirectory(path)) {
    320       NOTREACHED() << "Could not create base Pnacl directory.";
    321       return;
    322     }
    323   }
    324 
    325   Version current_version(kNullVersion);
    326   std::string current_fingerprint;
    327   std::vector<base::FilePath> older_dirs;
    328   if (GetLatestPnaclDirectory(pci, &path, &current_version, &older_dirs)) {
    329     scoped_ptr<base::DictionaryValue> manifest(ReadComponentManifest(path));
    330     scoped_ptr<base::DictionaryValue> pnacl_manifest(ReadPnaclManifest(path));
    331     Version manifest_version;
    332     // Check that the component manifest and PNaCl manifest files
    333     // are legit, and that the indicated version matches the one
    334     // encoded within the path name.
    335     if (manifest == NULL || pnacl_manifest == NULL ||
    336         !CheckPnaclComponentManifest(*manifest,
    337                                      *pnacl_manifest,
    338                                      &manifest_version) ||
    339         !current_version.Equals(manifest_version)) {
    340       current_version = Version(kNullVersion);
    341     } else {
    342       OverrideDirPnaclComponent(path);
    343       base::ReadFileToString(path.AppendASCII("manifest.fingerprint"),
    344                              &current_fingerprint);
    345     }
    346   }
    347 
    348   // If updates are disabled, only discover the current version
    349   // and OverrideDirPnaclComponent. That way, developers can use
    350   // a pinned version. Do not actually finish registration with
    351   // the component update service.
    352   if (pci->updates_disabled())
    353     return;
    354 
    355   BrowserThread::PostTask(BrowserThread::UI,
    356                           FROM_HERE,
    357                           base::Bind(&FinishPnaclUpdateRegistration,
    358                                      current_version,
    359                                      current_fingerprint,
    360                                      pci));
    361 
    362   // Remove older versions of PNaCl.
    363   for (std::vector<base::FilePath>::iterator iter = older_dirs.begin();
    364        iter != older_dirs.end();
    365        ++iter) {
    366     base::DeleteFile(*iter, true);
    367   }
    368 }
    369 
    370 }  // namespace
    371 
    372 void PnaclComponentInstaller::RegisterPnaclComponent(
    373     ComponentUpdateService* cus,
    374     const CommandLine& command_line) {
    375   // Register PNaCl by default (can be disabled).
    376   updates_disabled_ = command_line.HasSwitch(switches::kDisablePnaclInstall);
    377   cus_ = cus;
    378   BrowserThread::PostTask(BrowserThread::FILE,
    379                           FROM_HERE,
    380                           base::Bind(&StartPnaclUpdateRegistration, this));
    381 }
    382 
    383 }  // namespace component_updater
    384 
    385 namespace pnacl {
    386 
    387 bool NeedsOnDemandUpdate() {
    388   return base::subtle::NoBarrier_Load(
    389              &component_updater::needs_on_demand_update) != 0;
    390 }
    391 
    392 }  // namespace pnacl
    393