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