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