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 "base/base_paths.h"
      8 #include "base/bind.h"
      9 #include "base/callback.h"
     10 #include "base/command_line.h"
     11 #include "base/compiler_specific.h"
     12 #include "base/file_util.h"
     13 #include "base/files/file_enumerator.h"
     14 #include "base/files/file_path.h"
     15 #include "base/json/json_file_value_serializer.h"
     16 #include "base/logging.h"
     17 #include "base/path_service.h"
     18 #include "base/strings/string_util.h"
     19 #include "base/values.h"
     20 #include "base/version.h"
     21 #include "base/win/windows_version.h"
     22 #include "build/build_config.h"
     23 #include "chrome/browser/browser_process.h"
     24 #include "chrome/browser/component_updater/component_updater_service.h"
     25 #include "chrome/browser/profiles/profile.h"
     26 #include "chrome/browser/profiles/profile_manager.h"
     27 #include "chrome/common/chrome_paths.h"
     28 #include "chrome/common/chrome_switches.h"
     29 #include "chrome/common/omaha_query_params/omaha_query_params.h"
     30 #include "content/public/browser/browser_thread.h"
     31 
     32 using chrome::OmahaQueryParams;
     33 using content::BrowserThread;
     34 
     35 namespace {
     36 
     37 // Name of the Pnacl component specified in the manifest.
     38 const char kPnaclManifestName[] = "PNaCl Translator";
     39 
     40 // Sanitize characters from Pnacl Arch value so that they can be used
     41 // in path names.  This should only be characters in the set: [a-z0-9_].
     42 // Keep in sync with chrome/browser/nacl_host/nacl_file_host.
     43 std::string SanitizeForPath(const std::string& input) {
     44   std::string result;
     45   ReplaceChars(input, "-", "_", &result);
     46   return result;
     47 }
     48 
     49 // Set the component's hash to the multi-CRX PNaCl package.
     50 void SetPnaclHash(CrxComponent* component) {
     51  static const uint8 sha256_hash[32] =
     52      { // This corresponds to AppID: hnimpnehoodheedghdeeijklkeaacbdc
     53        0x7d, 0x8c, 0xfd, 0x47, 0xee, 0x37, 0x44, 0x36, 0x73, 0x44,
     54        0x89, 0xab, 0xa4, 0x00, 0x21, 0x32, 0x4a, 0x06, 0x06, 0xf1, 0x51,
     55        0x3c, 0x51, 0xba, 0x31, 0x2f, 0xbc, 0xb3, 0x99, 0x07, 0xdc, 0x9c};
     56 
     57   component->pk_hash.assign(sha256_hash,
     58                             &sha256_hash[arraysize(sha256_hash)]);
     59 }
     60 
     61 // If we don't have Pnacl installed, this is the version we claim.
     62 const char kNullVersion[] = "0.0.0.0";
     63 
     64 // PNaCl is packaged as a multi-CRX.  This returns the platform-specific
     65 // subdirectory that is part of that multi-CRX.
     66 base::FilePath GetPlatformDir(const base::FilePath& base_path) {
     67   std::string arch = SanitizeForPath(OmahaQueryParams::getNaclArch());
     68   return base_path.AppendASCII("_platform_specific").AppendASCII(arch);
     69 }
     70 
     71 // Tell the rest of the world where to find the platform-specific PNaCl files.
     72 void OverrideDirPnaclComponent(const base::FilePath& base_path) {
     73   PathService::Override(chrome::DIR_PNACL_COMPONENT,
     74                         GetPlatformDir(base_path));
     75 }
     76 
     77 bool GetLatestPnaclDirectory(PnaclComponentInstaller* pci,
     78                              base::FilePath* latest_dir,
     79                              Version* latest_version,
     80                              std::vector<base::FilePath>* older_dirs) {
     81   // Enumerate all versions starting from the base directory.
     82   base::FilePath base_dir = pci->GetPnaclBaseDirectory();
     83   bool found = false;
     84   base::FileEnumerator
     85       file_enumerator(base_dir, false, base::FileEnumerator::DIRECTORIES);
     86   for (base::FilePath path = file_enumerator.Next(); !path.value().empty();
     87        path = file_enumerator.Next()) {
     88     Version version(path.BaseName().MaybeAsASCII());
     89     if (!version.IsValid())
     90       continue;
     91     if (found) {
     92       if (version.CompareTo(*latest_version) > 0) {
     93         older_dirs->push_back(*latest_dir);
     94         *latest_dir = path;
     95         *latest_version = version;
     96       } else {
     97         older_dirs->push_back(path);
     98       }
     99     } else {
    100       *latest_version = version;
    101       *latest_dir = path;
    102       found = true;
    103     }
    104   }
    105   return found;
    106 }
    107 
    108 // Read a manifest file in.
    109 base::DictionaryValue* ReadJSONManifest(
    110     const base::FilePath& manifest_path) {
    111   JSONFileValueSerializer serializer(manifest_path);
    112   std::string error;
    113   scoped_ptr<base::Value> root(serializer.Deserialize(NULL, &error));
    114   if (!root.get())
    115     return NULL;
    116   if (!root->IsType(base::Value::TYPE_DICTIONARY))
    117     return NULL;
    118   return static_cast<base::DictionaryValue*>(root.release());
    119 }
    120 
    121 // Read the PNaCl specific manifest.
    122 base::DictionaryValue* ReadPnaclManifest(const base::FilePath& unpack_path) {
    123   base::FilePath manifest_path = GetPlatformDir(unpack_path).AppendASCII(
    124       "pnacl_public_pnacl_json");
    125   if (!base::PathExists(manifest_path))
    126     return NULL;
    127   return ReadJSONManifest(manifest_path);
    128 }
    129 
    130 // Read the component's manifest.json.
    131 base::DictionaryValue* ReadComponentManifest(
    132     const base::FilePath& unpack_path) {
    133   base::FilePath manifest_path = unpack_path.Append(
    134       FILE_PATH_LITERAL("manifest.json"));
    135   if (!base::PathExists(manifest_path))
    136     return NULL;
    137   return ReadJSONManifest(manifest_path);
    138 }
    139 
    140 // Check that the component's manifest is for PNaCl, and check the
    141 // PNaCl manifest indicates this is the correct arch-specific package.
    142 bool CheckPnaclComponentManifest(const base::DictionaryValue& manifest,
    143                                  const base::DictionaryValue& pnacl_manifest,
    144                                  Version* version_out) {
    145   // Make sure we have the right |manifest| file.
    146   std::string name;
    147   if (!manifest.GetStringASCII("name", &name)) {
    148     LOG(WARNING) << "'name' field is missing from manifest!";
    149     return false;
    150   }
    151   // For the webstore, we've given different names to each of the
    152   // architecture specific packages (and test/QA vs not test/QA)
    153   // so only part of it is the same.
    154   if (name.find(kPnaclManifestName) == std::string::npos) {
    155     LOG(WARNING) << "'name' field in manifest is invalid ("
    156                  << name << ") -- missing ("
    157                  << kPnaclManifestName << ")";
    158     return false;
    159   }
    160 
    161   std::string proposed_version;
    162   if (!manifest.GetStringASCII("version", &proposed_version)) {
    163     LOG(WARNING) << "'version' field is missing from manifest!";
    164     return false;
    165   }
    166   Version version(proposed_version.c_str());
    167   if (!version.IsValid()) {
    168     LOG(WARNING) << "'version' field in manifest is invalid "
    169                  << version.GetString();
    170     return false;
    171   }
    172 
    173   // Now check the |pnacl_manifest|.
    174   std::string arch;
    175   if (!pnacl_manifest.GetStringASCII("pnacl-arch", &arch)) {
    176     LOG(WARNING) << "'pnacl-arch' field is missing from pnacl-manifest!";
    177     return false;
    178   }
    179   if (arch.compare(OmahaQueryParams::getNaclArch()) != 0) {
    180     LOG(WARNING) << "'pnacl-arch' field in manifest is invalid ("
    181                  << arch << " vs " << OmahaQueryParams::getNaclArch() << ")";
    182     return false;
    183   }
    184 
    185   *version_out = version;
    186   return true;
    187 }
    188 
    189 }  // namespace
    190 
    191 PnaclComponentInstaller::PnaclComponentInstaller()
    192     : per_user_(false),
    193       updates_disabled_(false),
    194       cus_(NULL) {
    195 #if defined(OS_CHROMEOS)
    196   per_user_ = true;
    197 #endif
    198   updater_observer_.reset(new PnaclUpdaterObserver(this));
    199 }
    200 
    201 PnaclComponentInstaller::~PnaclComponentInstaller() {
    202 }
    203 
    204 void PnaclComponentInstaller::OnUpdateError(int error) {
    205   NOTREACHED() << "Pnacl update error: " << error;
    206 }
    207 
    208 // Pnacl components have the version encoded in the path itself:
    209 // <profile>\AppData\Local\Google\Chrome\User Data\pnacl\0.1.2.3\.
    210 // and the base directory will be:
    211 // <profile>\AppData\Local\Google\Chrome\User Data\pnacl\.
    212 base::FilePath PnaclComponentInstaller::GetPnaclBaseDirectory() {
    213   // For ChromeOS, temporarily make this user-dependent (for integrity) until
    214   // we find a better solution.
    215   // This is not ideal because of the following:
    216   //   (a) We end up with per-user copies instead of a single copy
    217   //   (b) The profile can change as users log in to different accounts
    218   //   so we need to watch for user-login-events (see pnacl_profile_observer.h).
    219   if (per_user_) {
    220     DCHECK(!current_profile_path_.empty());
    221     base::FilePath path = current_profile_path_.Append(
    222         FILE_PATH_LITERAL("pnacl"));
    223     return path;
    224   } else {
    225     base::FilePath result;
    226     CHECK(PathService::Get(chrome::DIR_PNACL_BASE, &result));
    227     return result;
    228   }
    229 }
    230 
    231 void PnaclComponentInstaller::OnProfileChange() {
    232   // On chromeos, we want to find the --login-profile=<foo> dir.
    233   // Even though the path does vary between users, the content
    234   // changes when logging out and logging in.
    235   ProfileManager* pm = g_browser_process->profile_manager();
    236   current_profile_path_ = pm->user_data_dir().Append(
    237       pm->GetInitialProfileDir());
    238 }
    239 
    240 bool PnaclComponentInstaller::Install(const base::DictionaryValue& manifest,
    241                                       const base::FilePath& unpack_path) {
    242   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    243   scoped_ptr<base::DictionaryValue> pnacl_manifest(
    244       ReadPnaclManifest(unpack_path));
    245   if (pnacl_manifest == NULL) {
    246     LOG(WARNING) << "Failed to read pnacl manifest.";
    247     NotifyInstallError();
    248     return false;
    249   }
    250 
    251   Version version;
    252   if (!CheckPnaclComponentManifest(manifest, *pnacl_manifest, &version)) {
    253     LOG(WARNING) << "CheckPnaclComponentManifest failed, not installing.";
    254     NotifyInstallError();
    255     return false;
    256   }
    257 
    258   // Don't install if the current version is actually newer.
    259   if (current_version().CompareTo(version) > 0) {
    260     NotifyInstallError();
    261     return false;
    262   }
    263 
    264   // Passed the basic tests. Time to install it.
    265   base::FilePath path = GetPnaclBaseDirectory().AppendASCII(
    266       version.GetString());
    267   if (base::PathExists(path)) {
    268     LOG(WARNING) << "Target path already exists, not installing.";
    269     NotifyInstallError();
    270     return false;
    271   }
    272   if (!base::Move(unpack_path, path)) {
    273     LOG(WARNING) << "Move failed, not installing.";
    274     NotifyInstallError();
    275     return false;
    276   }
    277 
    278   // Installation is done. Now tell the rest of chrome.
    279   // - The path service.
    280   // - Callbacks that requested an update.
    281   set_current_version(version);
    282   NotifyInstallSuccess();
    283   OverrideDirPnaclComponent(path);
    284   return true;
    285 }
    286 
    287 // Given |file|, which can be a path like "_platform_specific/arm/pnacl_foo",
    288 // returns the assumed install path. The path separator in |file| is '/'
    289 // for all platforms. Caller is responsible for checking that the
    290 // |installed_file| actually exists.
    291 bool PnaclComponentInstaller::GetInstalledFile(
    292     const std::string& file, base::FilePath* installed_file) {
    293   if (current_version().Equals(Version(kNullVersion)))
    294     return false;
    295 
    296   *installed_file = GetPnaclBaseDirectory().AppendASCII(
    297       current_version().GetString()).AppendASCII(file);
    298   return true;
    299 }
    300 
    301 void PnaclComponentInstaller::NotifyInstallError() {
    302   if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
    303     BrowserThread::PostTask(
    304         BrowserThread::UI, FROM_HERE,
    305         base::Bind(&PnaclComponentInstaller::NotifyInstallError,
    306                    // Unretained because installer lives until process shutdown.
    307                    base::Unretained(this)));
    308     return;
    309   }
    310   if (!install_callback_.is_null()) {
    311     updater_observer_->StopObserving();
    312     install_callback_.Run(false);
    313     install_callback_.Reset();
    314   }
    315 }
    316 
    317 void PnaclComponentInstaller::NotifyInstallSuccess() {
    318   if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
    319     BrowserThread::PostTask(
    320         BrowserThread::UI, FROM_HERE,
    321         base::Bind(&PnaclComponentInstaller::NotifyInstallSuccess,
    322                    // Unretained because installer lives until process shutdown.
    323                    base::Unretained(this)));
    324     return;
    325   }
    326   if (!install_callback_.is_null()) {
    327     updater_observer_->StopObserving();
    328     install_callback_.Run(true);
    329     install_callback_.Reset();
    330   }
    331 }
    332 
    333 CrxComponent PnaclComponentInstaller::GetCrxComponent() {
    334   CrxComponent pnacl_component;
    335   pnacl_component.version = current_version();
    336   pnacl_component.name = "pnacl";
    337   pnacl_component.installer = this;
    338   pnacl_component.observer = updater_observer_.get();
    339   SetPnaclHash(&pnacl_component);
    340 
    341   return pnacl_component;
    342 }
    343 
    344 namespace {
    345 
    346 void FinishPnaclUpdateRegistration(const Version& current_version,
    347                                    PnaclComponentInstaller* pci) {
    348   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    349   pci->set_current_version(current_version);
    350   CrxComponent pnacl_component = pci->GetCrxComponent();
    351 
    352   ComponentUpdateService::Status status =
    353       pci->cus()->RegisterComponent(pnacl_component);
    354   if (status != ComponentUpdateService::kOk
    355       && status != ComponentUpdateService::kReplaced) {
    356     NOTREACHED() << "Pnacl component registration failed.";
    357   }
    358 }
    359 
    360 // Check if there is an existing version on disk first to know when
    361 // a hosted version is actually newer.
    362 void StartPnaclUpdateRegistration(PnaclComponentInstaller* pci) {
    363   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    364   base::FilePath path = pci->GetPnaclBaseDirectory();
    365   if (!base::PathExists(path)) {
    366     if (!file_util::CreateDirectory(path)) {
    367       NOTREACHED() << "Could not create base Pnacl directory.";
    368       return;
    369     }
    370   }
    371 
    372   Version version(kNullVersion);
    373   std::vector<base::FilePath> older_dirs;
    374   if (GetLatestPnaclDirectory(pci, &path, &version, &older_dirs)) {
    375     scoped_ptr<base::DictionaryValue> manifest(
    376         ReadComponentManifest(path));
    377     scoped_ptr<base::DictionaryValue> pnacl_manifest(
    378         ReadPnaclManifest(path));
    379     Version manifest_version;
    380     // Check that the component manifest and PNaCl manifest files
    381     // are legit, and that the indicated version matches the one
    382     // encoded within the path name.
    383     if (manifest == NULL || pnacl_manifest == NULL
    384         || !CheckPnaclComponentManifest(*manifest,
    385                                         *pnacl_manifest,
    386                                         &manifest_version)
    387         || !version.Equals(manifest_version)) {
    388       version = Version(kNullVersion);
    389     } else {
    390       OverrideDirPnaclComponent(path);
    391     }
    392   }
    393 
    394   // If updates are disabled, only discover the current version
    395   // and OverrideDirPnaclComponent. That way, developers can use
    396   // a pinned version. Do not actually finish registration with
    397   // the component update service.
    398   if (pci->updates_disabled())
    399     return;
    400 
    401   BrowserThread::PostTask(
    402       BrowserThread::UI, FROM_HERE,
    403       base::Bind(&FinishPnaclUpdateRegistration, version, pci));
    404 
    405   // Remove older versions of PNaCl.
    406   for (std::vector<base::FilePath>::iterator iter = older_dirs.begin();
    407        iter != older_dirs.end(); ++iter) {
    408     base::DeleteFile(*iter, true);
    409   }
    410 }
    411 
    412 // Remove old per-profile copies of PNaCl (was for ChromeOS).
    413 // TODO(jvoung): Delete this code once most ChromeOS users have reaped
    414 // their old per-profile copies of PNaCl.
    415 void ReapOldChromeOSPnaclFiles(PnaclComponentInstaller* pci) {
    416   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    417   base::FilePath path = pci->GetPnaclBaseDirectory();
    418   if (!base::PathExists(path))
    419     return;
    420 
    421   // Do a basic sanity check first.
    422   if (pci->per_user()
    423       && path.BaseName().value().compare(FILE_PATH_LITERAL("pnacl")) == 0)
    424     base::DeleteFile(path, true);
    425 }
    426 
    427 
    428 void GetProfileInformation(PnaclComponentInstaller* pci) {
    429   // Bail if not logged in yet.
    430   if (!g_browser_process->profile_manager()->IsLoggedIn()) {
    431     return;
    432   }
    433 
    434   pci->OnProfileChange();
    435 
    436   // Do not actually register PNaCl for component updates, for CHROMEOS.
    437   // Just get the profile information and delete the per-profile files
    438   // if they exist.
    439  BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
    440                          base::Bind(&ReapOldChromeOSPnaclFiles, pci));
    441 }
    442 
    443 }  // namespace
    444 
    445 void PnaclComponentInstaller::RegisterPnaclComponent(
    446                             ComponentUpdateService* cus,
    447                             const CommandLine& command_line) {
    448   // Register PNaCl by default (can be disabled).
    449   updates_disabled_ = command_line.HasSwitch(switches::kDisablePnaclInstall);
    450   cus_ = cus;
    451   // If per_user, create a profile observer to watch for logins.
    452   // Only do so after cus_ is set to something non-null.
    453   if (per_user_ && !profile_observer_) {
    454     profile_observer_.reset(new PnaclProfileObserver(this));
    455   }
    456   if (per_user_) {
    457     // Figure out profile information, before proceeding to look for files.
    458     BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
    459                             base::Bind(&GetProfileInformation, this));
    460   } else {
    461     BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
    462                             base::Bind(&StartPnaclUpdateRegistration, this));
    463   }
    464 }
    465 
    466 void PnaclComponentInstaller::ReRegisterPnacl() {
    467   DCHECK(per_user_);
    468   // Figure out profile information, before proceeding to look for files.
    469   BrowserThread::PostTask(
    470       BrowserThread::UI, FROM_HERE,
    471       base::Bind(&GetProfileInformation, this));
    472 }
    473 
    474 void PnaclComponentInstaller::RequestFirstInstall(const InstallCallback& cb) {
    475   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    476   // Only one request can happen at once.
    477   if (!install_callback_.is_null()) {
    478     cb.Run(false);
    479     return;
    480   }
    481   set_current_version(Version(kNullVersion));
    482   CrxComponent pnacl_component = GetCrxComponent();
    483   ComponentUpdateService::Status status = cus_->CheckForUpdateSoon(
    484       pnacl_component);
    485   if (status != ComponentUpdateService::kOk) {
    486     cb.Run(false);
    487     return;
    488   }
    489   install_callback_ = cb;
    490   updater_observer_->EnsureObserving();
    491 }
    492