pnacl_component_installer.cc revision 868fa2fe829687343ffae624259930155e16dbd8
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/command_line.h" 10#include "base/compiler_specific.h" 11#include "base/file_util.h" 12#include "base/files/file_enumerator.h" 13#include "base/files/file_path.h" 14#include "base/json/json_file_value_serializer.h" 15#include "base/logging.h" 16#include "base/path_service.h" 17#include "base/strings/string_util.h" 18#include "base/values.h" 19#include "base/version.h" 20#include "base/win/windows_version.h" 21#include "build/build_config.h" 22#include "chrome/browser/browser_process.h" 23#include "chrome/browser/component_updater/component_updater_service.h" 24#include "chrome/browser/profiles/profile.h" 25#include "chrome/browser/profiles/profile_manager.h" 26#include "chrome/common/chrome_paths.h" 27#include "chrome/common/chrome_switches.h" 28#include "chrome/common/omaha_query_params/omaha_query_params.h" 29#include "content/public/browser/browser_thread.h" 30 31using chrome::OmahaQueryParams; 32using content::BrowserThread; 33 34namespace { 35 36// If PNaCl isn't installed yet, but a user is running chrome with 37// --enable-pnacl, this is the amount of time to wait before starting 38// a background install. 39const int kInitialDelaySeconds = 10; 40 41// Name of the Pnacl component specified in the manifest. 42const char kPnaclManifestNamePrefix[] = "PNaCl"; 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. 47std::string SanitizeForPath(const std::string& input) { 48 std::string result; 49 ReplaceChars(input, "-", "_", &result); 50 return result; 51} 52 53// Set the component's hash to the arch-specific PNaCl package. 54void SetPnaclHash(CrxComponent* component) { 55#if defined(ARCH_CPU_X86_FAMILY) 56 // Define both x86_32 and x86_64, and choose below. 57 static const uint8 x86_sha256_hash[][32] = { 58 { // This corresponds to AppID (x86-32): aealhdcgieaiikaifafholmmeooeeioj 59 0x04, 0x0b, 0x73, 0x26, 0x84, 0x08, 0x8a, 0x08, 0x50, 0x57, 60 0xeb, 0xcc, 0x4e, 0xe4, 0x48, 0xe9, 0x44, 0x2c, 0xc8, 0xa6, 0xd6, 61 0x96, 0x11, 0xd4, 0x2a, 0xc5, 0x26, 0x64, 0x34, 0x76, 0x3d, 0x14}, 62 { // This corresponds to AppID (x86-64): knlfebnofcjjnkpkapbgfphaagefndik 63 0xad, 0xb5, 0x41, 0xde, 0x52, 0x99, 0xda, 0xfa, 0x0f, 0x16, 64 0x5f, 0x70, 0x06, 0x45, 0xd3, 0x8a, 0x32, 0x20, 0x84, 0x57, 0x5c, 65 0x1f, 0xef, 0xb4, 0x42, 0x32, 0xce, 0x4a, 0x3c, 0x2d, 0x7e, 0x3a} 66 }; 67 68#if defined(ARCH_CPU_X86_64) 69 component->pk_hash.assign( 70 x86_sha256_hash[1], 71 &x86_sha256_hash[1][sizeof(x86_sha256_hash[1])]); 72#elif defined(OS_WIN) 73 bool x86_64 = (base::win::OSInfo::GetInstance()->wow64_status() == 74 base::win::OSInfo::WOW64_ENABLED); 75 if (x86_64) { 76 component->pk_hash.assign( 77 x86_sha256_hash[1], 78 &x86_sha256_hash[1][sizeof(x86_sha256_hash[1])]); 79 } else { 80 component->pk_hash.assign( 81 x86_sha256_hash[0], 82 &x86_sha256_hash[0][sizeof(x86_sha256_hash[0])]); 83 } 84#else 85 component->pk_hash.assign( 86 x86_sha256_hash[0], 87 &x86_sha256_hash[0][sizeof(x86_sha256_hash[0])]); 88#endif 89#elif defined(ARCH_CPU_ARMEL) 90 // This corresponds to AppID: jgobdlakdbanalhiagkdgcnofkbebejj 91 static const uint8 arm_sha256_hash[] = { 92 0x96, 0xe1, 0x3b, 0x0a, 0x31, 0x0d, 0x0b, 0x78, 0x06, 0xa3, 93 0x62, 0xde, 0x5a, 0x14, 0x14, 0x99, 0xd4, 0xd9, 0x01, 0x85, 0xc6, 94 0x9a, 0xd2, 0x51, 0x90, 0xa4, 0xb4, 0x94, 0xbd, 0xb8, 0x8b, 0xe8}; 95 96 component->pk_hash.assign(arm_sha256_hash, 97 &arm_sha256_hash[sizeof(arm_sha256_hash)]); 98#elif defined(ARCH_CPU_MIPSEL) 99 // This is a dummy CRX hash for MIPS, so that it will at least compile. 100 static const uint8 mips32_sha256_hash[] = { 101 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 102 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 103 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 104 105 component->pk_hash.assign(mips32_sha256_hash, 106 &mips32_sha256_hash[sizeof(mips32_sha256_hash)]); 107#else 108#error "Add support for your architecture to Pnacl Component Installer." 109#endif 110} 111 112 113// If we don't have Pnacl installed, this is the version we claim. 114const char kNullVersion[] = "0.0.0.0"; 115 116bool GetLatestPnaclDirectory(PnaclComponentInstaller* pci, 117 base::FilePath* latest_dir, 118 Version* latest_version, 119 std::vector<base::FilePath>* older_dirs) { 120 // Enumerate all versions starting from the base directory. 121 base::FilePath base_dir = pci->GetPnaclBaseDirectory(); 122 bool found = false; 123 base::FileEnumerator 124 file_enumerator(base_dir, false, base::FileEnumerator::DIRECTORIES); 125 for (base::FilePath path = file_enumerator.Next(); !path.value().empty(); 126 path = file_enumerator.Next()) { 127 Version version(path.BaseName().MaybeAsASCII()); 128 if (!version.IsValid()) 129 continue; 130 if (found) { 131 if (version.CompareTo(*latest_version) > 0) { 132 older_dirs->push_back(*latest_dir); 133 *latest_dir = path; 134 *latest_version = version; 135 } else { 136 older_dirs->push_back(path); 137 } 138 } else { 139 *latest_version = version; 140 *latest_dir = path; 141 found = true; 142 } 143 } 144 return found; 145} 146 147// Read a manifest file in. 148base::DictionaryValue* ReadJSONManifest( 149 const base::FilePath& manifest_path) { 150 JSONFileValueSerializer serializer(manifest_path); 151 std::string error; 152 scoped_ptr<base::Value> root(serializer.Deserialize(NULL, &error)); 153 if (!root.get()) 154 return NULL; 155 if (!root->IsType(base::Value::TYPE_DICTIONARY)) 156 return NULL; 157 return static_cast<base::DictionaryValue*>(root.release()); 158} 159 160// Read the PNaCl specific manifest. 161base::DictionaryValue* ReadPnaclManifest(const base::FilePath& unpack_path) { 162 base::FilePath manifest_path = unpack_path.Append( 163 FILE_PATH_LITERAL("pnacl_public_pnacl_json")); 164 if (!file_util::PathExists(manifest_path)) 165 return NULL; 166 return ReadJSONManifest(manifest_path); 167} 168 169// Read the component's manifest.json. 170base::DictionaryValue* ReadComponentManifest( 171 const base::FilePath& unpack_path) { 172 base::FilePath manifest_path = unpack_path.Append( 173 FILE_PATH_LITERAL("manifest.json")); 174 if (!file_util::PathExists(manifest_path)) 175 return NULL; 176 return ReadJSONManifest(manifest_path); 177} 178 179} // namespace 180 181// Check that the component's manifest is for PNaCl, and check the 182// PNaCl manifest indicates this is the correct arch-specific package. 183bool CheckPnaclComponentManifest(const base::DictionaryValue& manifest, 184 const base::DictionaryValue& pnacl_manifest, 185 Version* version_out) { 186 // Make sure we have the right |manifest| file. 187 std::string name; 188 manifest.GetStringASCII("name", &name); 189 // For the webstore, we've given different names to each of the 190 // architecture specific packages, so only the prefix is the same. 191 if (StartsWithASCII(kPnaclManifestNamePrefix, name, false)) { 192 LOG(WARNING) << "'name' field in manifest is invalid (" 193 << name << ") -- missing prefix (" 194 << kPnaclManifestNamePrefix << ")"; 195 return false; 196 } 197 198 std::string proposed_version; 199 manifest.GetStringASCII("version", &proposed_version); 200 Version version(proposed_version.c_str()); 201 if (!version.IsValid()) { 202 LOG(WARNING) << "'version' field in manifest is invalid " 203 << version.GetString(); 204 return false; 205 } 206 207 // Now check the |pnacl_manifest|. 208 std::string arch; 209 pnacl_manifest.GetStringASCII("pnacl-arch", &arch); 210 if (arch.compare(OmahaQueryParams::getNaclArch()) != 0) { 211 LOG(WARNING) << "'pnacl-arch' field in manifest is invalid (" 212 << arch << " vs " << OmahaQueryParams::getNaclArch() << ")"; 213 return false; 214 } 215 216 *version_out = version; 217 return true; 218} 219 220PnaclComponentInstaller::PnaclComponentInstaller() 221 : per_user_(false), 222 cus_(NULL) { 223#if defined(OS_CHROMEOS) 224 per_user_ = true; 225#endif 226} 227 228PnaclComponentInstaller::~PnaclComponentInstaller() { 229} 230 231void PnaclComponentInstaller::OnUpdateError(int error) { 232 NOTREACHED() << "Pnacl update error: " << error; 233} 234 235// Pnacl components have the version encoded in the path itself: 236// <profile>\AppData\Local\Google\Chrome\User Data\Pnacl\0.1.2.3\. 237// and the base directory will be: 238// <profile>\AppData\Local\Google\Chrome\User Data\Pnacl\. 239base::FilePath PnaclComponentInstaller::GetPnaclBaseDirectory() { 240 // For ChromeOS, temporarily make this user-dependent (for integrity) until 241 // we find a better solution. 242 // This is not ideal because of the following: 243 // (a) We end up with per-user copies instead of a single copy 244 // (b) The profile can change as users log in to different accounts 245 // so we need to watch for user-login-events (see pnacl_profile_observer.h). 246 if (per_user_) { 247 DCHECK(!current_profile_path_.empty()); 248 base::FilePath path = current_profile_path_.Append( 249 FILE_PATH_LITERAL("pnacl")); 250 return path; 251 } else { 252 base::FilePath result; 253 CHECK(PathService::Get(chrome::DIR_PNACL_BASE, &result)); 254 return result; 255 } 256} 257 258void PnaclComponentInstaller::OnProfileChange() { 259 // On chromeos, we want to find the --login-profile=<foo> dir. 260 // Even though the path does vary between users, the content 261 // changes when logging out and logging in. 262 ProfileManager* pm = g_browser_process->profile_manager(); 263 current_profile_path_ = pm->user_data_dir().Append( 264 pm->GetInitialProfileDir()); 265} 266 267bool PnaclComponentInstaller::Install(const base::DictionaryValue& manifest, 268 const base::FilePath& unpack_path) { 269 scoped_ptr<base::DictionaryValue> pnacl_manifest( 270 ReadPnaclManifest(unpack_path)); 271 if (pnacl_manifest == NULL) { 272 LOG(WARNING) << "Failed to read pnacl manifest."; 273 return false; 274 } 275 276 Version version; 277 if (!CheckPnaclComponentManifest(manifest, *pnacl_manifest, &version)) { 278 LOG(WARNING) << "CheckPnaclComponentManifest failed, not installing."; 279 return false; 280 } 281 282 // Don't install if the current version is actually newer. 283 if (current_version().CompareTo(version) > 0) 284 return false; 285 286 // Passed the basic tests. Time to install it. 287 base::FilePath path = GetPnaclBaseDirectory().AppendASCII( 288 version.GetString()); 289 if (file_util::PathExists(path)) { 290 LOG(WARNING) << "Target path already exists, not installing."; 291 return false; 292 } 293 if (!file_util::Move(unpack_path, path)) { 294 LOG(WARNING) << "Move failed, not installing."; 295 return false; 296 } 297 298 // Installation is done. Now tell the rest of chrome (just the path service 299 // for now). TODO(jvoung): we need notifications if someone surfed to a 300 // Pnacl webpage and Pnacl was just installed at this time. They should 301 // then be able to reload the page and retry (or something). 302 // See: http://code.google.com/p/chromium/issues/detail?id=107438 303 set_current_version(version); 304 305 PathService::Override(chrome::DIR_PNACL_COMPONENT, path); 306 return true; 307} 308 309namespace { 310 311void DoCheckForUpdate(ComponentUpdateService* cus, 312 const CrxComponent& pnacl) { 313 if (cus->CheckForUpdateSoon(pnacl) != ComponentUpdateService::kOk) { 314 LOG(WARNING) << "Pnacl check for update failed."; 315 } 316} 317 318// Finally, do the registration with the right version number. 319void FinishPnaclUpdateRegistration(const Version& current_version, 320 PnaclComponentInstaller* pci) { 321 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 322 CrxComponent pnacl_component; 323 pnacl_component.version = current_version; 324 pnacl_component.name = "pnacl"; 325 pnacl_component.installer = pci; 326 pci->set_current_version(current_version); 327 SetPnaclHash(&pnacl_component); 328 329 ComponentUpdateService::Status status = 330 pci->cus()->RegisterComponent(pnacl_component); 331 if (status != ComponentUpdateService::kOk 332 && status != ComponentUpdateService::kReplaced) { 333 NOTREACHED() << "Pnacl component registration failed."; 334 } 335 336 // If PNaCl is not yet installed but it is requested by --enable-pnacl, 337 // we want it to be available "soon", so kick off an update check 338 // earlier than usual. 339 Version null_version(kNullVersion); 340 if (pci->current_version().Equals(null_version)) { 341 BrowserThread::PostDelayedTask( 342 BrowserThread::UI, FROM_HERE, 343 base::Bind(DoCheckForUpdate, pci->cus(), pnacl_component), 344 base::TimeDelta::FromSeconds(kInitialDelaySeconds)); 345 } 346} 347 348// Check if there is an existing version on disk first to know when 349// a hosted version is actually newer. 350void StartPnaclUpdateRegistration(PnaclComponentInstaller* pci) { 351 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 352 base::FilePath path = pci->GetPnaclBaseDirectory(); 353 if (!file_util::PathExists(path)) { 354 if (!file_util::CreateDirectory(path)) { 355 NOTREACHED() << "Could not create base Pnacl directory."; 356 return; 357 } 358 } 359 360 Version version(kNullVersion); 361 std::vector<base::FilePath> older_dirs; 362 if (GetLatestPnaclDirectory(pci, &path, &version, &older_dirs)) { 363 scoped_ptr<base::DictionaryValue> manifest( 364 ReadComponentManifest(path)); 365 scoped_ptr<base::DictionaryValue> pnacl_manifest( 366 ReadPnaclManifest(path)); 367 Version manifest_version; 368 // Check that the component manifest and PNaCl manifest files 369 // are legit, and that the indicated version matches the one 370 // encoded within the path name. 371 if (!CheckPnaclComponentManifest(*manifest, *pnacl_manifest, 372 &manifest_version) 373 || !version.Equals(manifest_version)) { 374 version = Version(kNullVersion); 375 } else { 376 PathService::Override(chrome::DIR_PNACL_COMPONENT, path); 377 } 378 } 379 380 BrowserThread::PostTask( 381 BrowserThread::UI, FROM_HERE, 382 base::Bind(&FinishPnaclUpdateRegistration, version, pci)); 383 384 // Remove older versions of PNaCl. 385 for (std::vector<base::FilePath>::iterator iter = older_dirs.begin(); 386 iter != older_dirs.end(); ++iter) { 387 file_util::Delete(*iter, true); 388 } 389} 390 391void GetProfileInformation(PnaclComponentInstaller* pci) { 392 // Bail if not logged in yet. 393 if (!g_browser_process->profile_manager()->IsLoggedIn()) { 394 return; 395 } 396 397 pci->OnProfileChange(); 398 399 BrowserThread::PostTask( 400 BrowserThread::FILE, FROM_HERE, 401 base::Bind(&StartPnaclUpdateRegistration, pci)); 402} 403 404 405} // namespace 406 407void PnaclComponentInstaller::RegisterPnaclComponent( 408 ComponentUpdateService* cus, 409 const CommandLine& command_line) { 410 // Only register when given the right flag. This is important since 411 // we do an early component updater check above (in DoCheckForUpdate). 412 if (command_line.HasSwitch(switches::kEnablePnacl)) { 413 cus_ = cus; 414 // If per_user, create a profile observer to watch for logins. 415 // Only do so after cus_ is set to something non-null. 416 if (per_user_ && !profile_observer_) { 417 profile_observer_.reset(new PnaclProfileObserver(this)); 418 } 419 if (per_user_) { 420 // Figure out profile information, before proceeding to look for files. 421 BrowserThread::PostTask( 422 BrowserThread::UI, FROM_HERE, 423 base::Bind(&GetProfileInformation, this)); 424 } else { 425 BrowserThread::PostTask( 426 BrowserThread::FILE, FROM_HERE, 427 base::Bind(&StartPnaclUpdateRegistration, this)); 428 } 429 } 430} 431 432void PnaclComponentInstaller::ReRegisterPnacl() { 433 // No need to check the commandline flags again here. 434 // We could only have gotten here after RegisterPnaclComponent 435 // found --enable-pnacl, since that is where we create the profile_observer_, 436 // which in turn calls ReRegisterPnacl. 437 DCHECK(per_user_); 438 // Figure out profile information, before proceeding to look for files. 439 BrowserThread::PostTask( 440 BrowserThread::UI, FROM_HERE, 441 base::Bind(&GetProfileInformation, this)); 442} 443