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