| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 * Copyright (c) 2011 The Native Client Authors. All rights reserved. | |
| 3 * Use of this source code is governed by a BSD-style license that can be | |
| 4 * found in the LICENSE file. | |
| 5 */ | |
| 6 | |
| 7 #include <algorithm> | |
| 8 | |
| 9 #include "native_client/src/trusted/plugin/manifest.h" | |
| 10 | |
| 11 #include <stdlib.h> | |
| 12 | |
| 13 #include "native_client/src/include/nacl_base.h" | |
| 14 #include "native_client/src/include/nacl_macros.h" | |
| 15 #include "native_client/src/include/nacl_string.h" | |
| 16 #include "native_client/src/include/portability.h" | |
| 17 #include "native_client/src/shared/platform/nacl_check.h" | |
| 18 #include "native_client/src/trusted/plugin/plugin_error.h" | |
| 19 #include "native_client/src/trusted/plugin/utility.h" | |
| 20 #include "native_client/src/third_party_mod/jsoncpp/include/json/reader.h" | |
| 21 #include "ppapi/cpp/dev/url_util_dev.h" | |
| 22 #include "ppapi/cpp/var.h" | |
| 23 | |
| 24 namespace plugin { | |
| 25 | |
| 26 namespace { | |
| 27 // Top-level section name keys | |
| 28 const char* const kProgramKey = "program"; | |
| 29 const char* const kInterpreterKey = "interpreter"; | |
| 30 const char* const kFilesKey = "files"; | |
| 31 | |
| 32 // ISA Dictionary keys | |
| 33 const char* const kX8632Key = "x86-32"; | |
| 34 const char* const kX8664Key = "x86-64"; | |
| 35 const char* const kArmKey = "arm"; | |
| 36 const char* const kPortableKey = "portable"; | |
| 37 const char* const kUrlKey = "url"; | |
| 38 | |
| 39 // Sample manifest file: | |
| 40 // { | |
| 41 // "program": { | |
| 42 // "x86-32": {"url": "myprogram_x86-32.nexe"}, | |
| 43 // "x86-64": {"url": "myprogram_x86-64.nexe"}, | |
| 44 // "arm": {"url": "myprogram_arm.nexe"}, | |
| 45 // "portable": {"url": "myprogram.pexe"} | |
| 46 // }, | |
| 47 // "interpreter": { | |
| 48 // "x86-32": {"url": "interpreter_x86-32.nexe"}, | |
| 49 // "x86-64": {"url": "interpreter_x86-64.nexe"}, | |
| 50 // "arm": {"url": "interpreter_arm.nexe"} | |
| 51 // }, | |
| 52 // "files": { | |
| 53 // "foo.txt": { | |
| 54 // "portable": {"url": "foo.txt"} | |
| 55 // }, | |
| 56 // "bar.txt": { | |
| 57 // "x86-32": {"url": "x86-32/bar.txt"}, | |
| 58 // "portable": {"url": "bar.txt"} | |
| 59 // } | |
| 60 // } | |
| 61 // } | |
| 62 | |
| 63 // TODO(jvoung): Remove these when we find a better way to store/install them. | |
| 64 const char* const kPnaclLlcKey = "pnacl-llc"; | |
| 65 const char* const kPnaclLdKey = "pnacl-ld"; | |
| 66 | |
| 67 // Looks up |property_name| in the vector |valid_names| with length | |
| 68 // |valid_name_count|. Returns true if |property_name| is found. | |
| 69 bool FindMatchingProperty(nacl::string property_name, | |
| 70 const char** valid_names, | |
| 71 size_t valid_name_count) { | |
| 72 for (size_t i = 0; i < valid_name_count; ++i) { | |
| 73 if (property_name == valid_names[i]) { | |
| 74 return true; | |
| 75 } | |
| 76 } | |
| 77 return false; | |
| 78 } | |
| 79 | |
| 80 // Validates that |dictionary| is a valid ISA dictionary. An ISA dictionary | |
| 81 // is validated to have keys from within the set of recognized ISAs. Unknown | |
| 82 // ISAs are allowed, but ignored and warnings are produced. It is also validated | |
| 83 // that it must have an entry to match the ISA specified in |sandbox_isa| or | |
| 84 // have a fallback 'portable' entry if there is no match. Returns true if | |
| 85 // |dictionary| is an ISA to URL map. Sets |error_string| to something | |
| 86 // descriptive if it fails. | |
| 87 bool IsValidISADictionary(const Json::Value& dictionary, | |
| 88 const nacl::string& sandbox_isa, | |
| 89 nacl::string* error_string) { | |
| 90 if (error_string == NULL) | |
| 91 return false; | |
| 92 | |
| 93 // An ISA to URL dictionary has to be an object. | |
| 94 if (!dictionary.isObject()) { | |
| 95 *error_string = " property is not an ISA to URL dictionary"; | |
| 96 return false; | |
| 97 } | |
| 98 // The keys to the dictionary have to be valid ISA names. | |
| 99 Json::Value::Members members = dictionary.getMemberNames(); | |
| 100 for (size_t i = 0; i < members.size(); ++i) { | |
| 101 // The known ISA values for ISA dictionaries in the manifest. | |
| 102 static const char* kManifestISAProperties[] = { | |
| 103 kX8632Key, | |
| 104 kX8664Key, | |
| 105 kArmKey, | |
| 106 kPortableKey | |
| 107 }; | |
| 108 nacl::string property_name = members[i]; | |
| 109 if (!FindMatchingProperty(property_name, | |
| 110 kManifestISAProperties, | |
| 111 NACL_ARRAY_SIZE(kManifestISAProperties))) { | |
| 112 PLUGIN_PRINTF(("IsValidISADictionary: unrecognized ISA '%s'.\n", | |
| 113 property_name.c_str())); | |
| 114 } | |
| 115 Json::Value url_spec = dictionary[property_name]; | |
| 116 if (!url_spec.isObject()) { | |
| 117 *error_string = " ISA property '" + property_name + | |
| 118 "' has non-dictionary value'" + url_spec.toStyledString() + "'."; | |
| 119 return false; | |
| 120 } | |
| 121 // Check properties of the arch-specific 'URL spec' dictionary | |
| 122 static const char* kManifestUrlSpecProperties[] = { | |
| 123 kUrlKey | |
| 124 }; | |
| 125 Json::Value::Members ISA_members = url_spec.getMemberNames(); | |
| 126 for (size_t j = 0; j < ISA_members.size(); ++j) { | |
| 127 nacl::string ISA_property_name = ISA_members[j]; | |
| 128 if (!FindMatchingProperty(ISA_property_name, | |
| 129 kManifestUrlSpecProperties, | |
| 130 NACL_ARRAY_SIZE(kManifestUrlSpecProperties))) { | |
| 131 PLUGIN_PRINTF(("IsValidISADictionary: unrecognized property for '%s'" | |
| 132 ": '%s'.", | |
| 133 property_name.c_str(), ISA_property_name.c_str())); | |
| 134 } | |
| 135 } | |
| 136 // A "url" key is required for each URL spec. | |
| 137 if (!url_spec.isMember(kUrlKey)) { | |
| 138 *error_string = " ISA property '" + property_name + | |
| 139 "' has no key '" + kUrlKey + "'."; | |
| 140 return false; | |
| 141 } | |
| 142 Json::Value url = url_spec[kUrlKey]; | |
| 143 if (!url.isString()) { | |
| 144 *error_string = " ISA property '" + property_name + | |
| 145 "' has non-string value '" + url.toStyledString() + | |
| 146 "' for key '" + kUrlKey + "'."; | |
| 147 return false; | |
| 148 } | |
| 149 } | |
| 150 | |
| 151 // TODO(elijahtaylor) add ISA resolver here if we expand ISAs to include | |
| 152 // micro-architectures that can resolve to multiple valid sandboxes. | |
| 153 bool has_isa = dictionary.isMember(sandbox_isa); | |
| 154 bool has_portable = dictionary.isMember(kPortableKey); | |
| 155 | |
| 156 if (!has_isa && !has_portable) { | |
| 157 *error_string = | |
| 158 " no version given for current arch and no portable version found."; | |
| 159 return false; | |
| 160 } | |
| 161 | |
| 162 return true; | |
| 163 } | |
| 164 | |
| 165 bool GetURLFromISADictionary(const Json::Value& dictionary, | |
| 166 const nacl::string& sandbox_isa, | |
| 167 nacl::string* url, | |
| 168 nacl::string* error_string, | |
| 169 bool* is_portable) { | |
| 170 if (url == NULL || error_string == NULL || is_portable == NULL) | |
| 171 return false; | |
| 172 | |
| 173 if (!IsValidISADictionary(dictionary, sandbox_isa, error_string)) | |
| 174 return false; | |
| 175 | |
| 176 const char* isa_string; | |
| 177 // The call to IsValidISADictionary() above guarantees that either | |
| 178 // sandbox_isa or kPortableKey is present in the dictionary. | |
| 179 bool has_portable = dictionary.isMember(kPortableKey); | |
| 180 bool has_isa = dictionary.isMember(sandbox_isa); | |
| 181 if ((has_portable && Manifest::PreferPortable()) || !has_isa) { | |
| 182 *is_portable = true; | |
| 183 isa_string = kPortableKey; | |
| 184 } else { | |
| 185 *is_portable = false; | |
| 186 isa_string = sandbox_isa.c_str(); | |
| 187 } | |
| 188 | |
| 189 const Json::Value& json_url = dictionary[isa_string][kUrlKey]; | |
| 190 *url = json_url.asString(); | |
| 191 | |
| 192 return true; | |
| 193 } | |
| 194 | |
| 195 // this will probably be replaced by jvoung's version that exposes | |
| 196 // is_portable checks | |
| 197 bool GetKeyUrl(const Json::Value& dictionary, | |
| 198 const nacl::string& key, | |
| 199 const nacl::string& sandbox_isa, | |
| 200 nacl::string* full_url, | |
| 201 nacl::string* error_string, | |
| 202 bool* is_portable) { | |
| 203 CHECK(full_url != NULL && error_string != NULL); | |
| 204 if (!dictionary.isMember(key)) { | |
| 205 *error_string = "file key not found in manifest"; | |
| 206 return false; | |
| 207 } | |
| 208 const Json::Value& isa_dict = dictionary[key]; | |
| 209 if (isa_dict.isMember(sandbox_isa)) { | |
| 210 *full_url = isa_dict[sandbox_isa][kUrlKey].asString(); | |
| 211 *is_portable = false; | |
| 212 return true; | |
| 213 } | |
| 214 if (isa_dict.isMember(kPortableKey)) { | |
| 215 *full_url = isa_dict[kPortableKey][kUrlKey].asString(); | |
| 216 *is_portable = true; | |
| 217 return true; | |
| 218 } | |
| 219 *error_string = "neither ISA-specific nor portable representations exist"; | |
| 220 return false; | |
| 221 } | |
| 222 | |
| 223 } // namespace | |
| 224 | |
| 225 bool Manifest::PreferPortable() { | |
| 226 return getenv("NACL_PREFER_PORTABLE_IN_MANIFEST") != NULL; | |
| 227 } | |
| 228 | |
| 229 bool Manifest::Init(const nacl::string& manifest_json, ErrorInfo* error_info) { | |
| 230 if (error_info == NULL) { | |
| 231 return false; | |
| 232 } | |
| 233 Json::Reader reader; | |
| 234 if (!reader.parse(manifest_json, dictionary_)) { | |
| 235 std::string json_error = reader.getFormatedErrorMessages(); | |
| 236 error_info->SetReport(ERROR_MANIFEST_PARSING, | |
| 237 "manifest JSON parsing failed: " + json_error); | |
| 238 return false; | |
| 239 } | |
| 240 // Parse has ensured the string was valid JSON. Check that it matches the | |
| 241 // manifest schema. | |
| 242 return MatchesSchema(error_info); | |
| 243 } | |
| 244 | |
| 245 bool Manifest::MatchesSchema(ErrorInfo* error_info) { | |
| 246 pp::Var exception; | |
| 247 if (error_info == NULL) { | |
| 248 return false; | |
| 249 } | |
| 250 if (!dictionary_.isObject()) { | |
| 251 error_info->SetReport( | |
| 252 ERROR_MANIFEST_SCHEMA_VALIDATE, | |
| 253 "manifest: is not a json dictionary."); | |
| 254 return false; | |
| 255 } | |
| 256 Json::Value::Members members = dictionary_.getMemberNames(); | |
| 257 for (size_t i = 0; i < members.size(); ++i) { | |
| 258 // The top level dictionary entries valid in the manifest file. | |
| 259 static const char* kManifestTopLevelProperties[] = { kProgramKey, | |
| 260 kInterpreterKey, | |
| 261 kFilesKey }; | |
| 262 nacl::string property_name = members[i]; | |
| 263 if (!FindMatchingProperty(property_name, | |
| 264 kManifestTopLevelProperties, | |
| 265 NACL_ARRAY_SIZE(kManifestTopLevelProperties))) { | |
| 266 PLUGIN_PRINTF(("Manifest::MatchesSchema: WARNING: unknown top-level " | |
| 267 "section '%s' in manifest.\n", property_name.c_str())); | |
| 268 } | |
| 269 } | |
| 270 | |
| 271 nacl::string error_string; | |
| 272 | |
| 273 // A manifest file must have a program section. | |
| 274 if (!dictionary_.isMember(kProgramKey)) { | |
| 275 error_info->SetReport( | |
| 276 ERROR_MANIFEST_SCHEMA_VALIDATE, | |
| 277 nacl::string("manifest: missing '") + kProgramKey + "' section."); | |
| 278 return false; | |
| 279 } | |
| 280 | |
| 281 // Validate the program section. | |
| 282 if (!IsValidISADictionary(dictionary_[kProgramKey], | |
| 283 sandbox_isa_, | |
| 284 &error_string)) { | |
| 285 error_info->SetReport( | |
| 286 ERROR_MANIFEST_SCHEMA_VALIDATE, | |
| 287 nacl::string("manifest: ") + kProgramKey + error_string); | |
| 288 return false; | |
| 289 } | |
| 290 | |
| 291 // Validate the interpreter section (if given). | |
| 292 if (dictionary_.isMember(kInterpreterKey)) { | |
| 293 if (!IsValidISADictionary(dictionary_[kProgramKey], | |
| 294 sandbox_isa_, | |
| 295 &error_string)) { | |
| 296 error_info->SetReport( | |
| 297 ERROR_MANIFEST_SCHEMA_VALIDATE, | |
| 298 nacl::string("manifest: ") + kInterpreterKey + error_string); | |
| 299 return false; | |
| 300 } | |
| 301 } | |
| 302 | |
| 303 // Validate the file dictionary (if given). | |
| 304 if (dictionary_.isMember(kFilesKey)) { | |
| 305 const Json::Value& files = dictionary_[kFilesKey]; | |
| 306 if (!files.isObject()) { | |
| 307 error_info->SetReport( | |
| 308 ERROR_MANIFEST_SCHEMA_VALIDATE, | |
| 309 nacl::string("manifest: '") + kFilesKey + "' is not a dictionary."); | |
| 310 } | |
| 311 Json::Value::Members members = files.getMemberNames(); | |
| 312 for (size_t i = 0; i < members.size(); ++i) { | |
| 313 nacl::string property_name = members[i]; | |
| 314 if (!IsValidISADictionary(files[property_name], | |
| 315 sandbox_isa_, | |
| 316 &error_string)) { | |
| 317 error_info->SetReport( | |
| 318 ERROR_MANIFEST_SCHEMA_VALIDATE, | |
| 319 nacl::string("manifest: file '") + property_name + "'" + | |
| 320 error_string); | |
| 321 return false; | |
| 322 } | |
| 323 } | |
| 324 } | |
| 325 | |
| 326 return true; | |
| 327 } | |
| 328 | |
| 329 bool Manifest::ResolveURL(const nacl::string& relative_url, | |
| 330 nacl::string* full_url, | |
| 331 ErrorInfo* error_info) const { | |
| 332 // The contents of the manifest are resolved relative to the manifest URL. | |
| 333 CHECK(url_util_ != NULL); | |
| 334 pp::Var resolved_url = | |
| 335 url_util_->ResolveRelativeToURL(pp::Var(manifest_base_url_), | |
| 336 relative_url); | |
| 337 if (!resolved_url.is_string()) { | |
| 338 error_info->SetReport( | |
| 339 ERROR_MANIFEST_RESOLVE_URL, | |
| 340 "could not resolve url '" + relative_url + | |
| 341 "' relative to manifest base url '" + manifest_base_url_.c_str() + | |
| 342 "'."); | |
| 343 return false; | |
| 344 } | |
| 345 *full_url = resolved_url.AsString(); | |
| 346 return true; | |
| 347 } | |
| 348 | |
| 349 bool Manifest::GetProgramURL(nacl::string* full_url, | |
| 350 ErrorInfo* error_info, | |
| 351 bool* is_portable) { | |
| 352 if (full_url == NULL || error_info == NULL || is_portable == NULL) | |
| 353 return false; | |
| 354 | |
| 355 Json::Value program = dictionary_[kProgramKey]; | |
| 356 | |
| 357 nacl::string nexe_url; | |
| 358 nacl::string error_string; | |
| 359 | |
| 360 if (!GetURLFromISADictionary(program, | |
| 361 sandbox_isa_, | |
| 362 &nexe_url, | |
| 363 &error_string, | |
| 364 is_portable)) { | |
| 365 error_info->SetReport(ERROR_MANIFEST_GET_NEXE_URL, | |
| 366 nacl::string("program:") + sandbox_isa_ + | |
| 367 error_string); | |
| 368 return false; | |
| 369 } | |
| 370 | |
| 371 return ResolveURL(nexe_url, full_url, error_info); | |
| 372 } | |
| 373 | |
| 374 bool Manifest::GetFileKeys(std::set<nacl::string>* keys) const { | |
| 375 if (!dictionary_.isMember(kFilesKey)) { | |
| 376 // trivial success: no keys when there is no "files" section. | |
| 377 return true; | |
| 378 } | |
| 379 const Json::Value& files = dictionary_[kFilesKey]; | |
| 380 CHECK(files.isObject()); | |
| 381 Json::Value::Members members = files.getMemberNames(); | |
| 382 for (size_t i = 0; i < members.size(); ++i) { | |
| 383 keys->insert(members[i]); | |
| 384 } | |
| 385 return true; | |
| 386 } | |
| 387 | |
| 388 bool Manifest::ResolveKey(const nacl::string& key, | |
| 389 nacl::string* full_url, | |
| 390 ErrorInfo* error_info, | |
| 391 bool* is_portable) const { | |
| 392 NaClLog(3, "Manifest::ResolveKey(%s)\n", key.c_str()); | |
| 393 // key must be one of kProgramKey or kFileKey '/' file-section-key | |
| 394 | |
| 395 *full_url = ""; | |
| 396 if (key == kProgramKey) { | |
| 397 nacl::string error_string; | |
| 398 if (!GetKeyUrl(dictionary_, key, sandbox_isa_, | |
| 399 full_url, &error_string, is_portable)) { | |
| 400 error_info->SetReport(ERROR_MANIFEST_RESOLVE_URL, error_string); | |
| 401 return false; | |
| 402 } | |
| 403 return true; | |
| 404 } | |
| 405 nacl::string::const_iterator p = find(key.begin(), key.end(), '/'); | |
| 406 if (p == key.end()) { | |
| 407 error_info->SetReport(ERROR_MANIFEST_RESOLVE_URL, | |
| 408 nacl::string("ResolveKey: invalid key, no slash: ") | |
| 409 + key); | |
| 410 return false; | |
| 411 } | |
| 412 | |
| 413 // generalize to permit other sections? | |
| 414 nacl::string prefix(key.begin(), p); | |
| 415 if (prefix != kFilesKey) { | |
| 416 error_info->SetReport(ERROR_MANIFEST_RESOLVE_URL, | |
| 417 nacl::string("ResolveKey: invalid key: not \"files\"" | |
| 418 " prefix: ") + key); | |
| 419 return false; | |
| 420 } | |
| 421 | |
| 422 nacl::string rest(p + 1, key.end()); | |
| 423 | |
| 424 const Json::Value& files = dictionary_[kFilesKey]; | |
| 425 CHECK(files.isObject()); | |
| 426 if (!files.isMember(rest)) { | |
| 427 error_info->SetReport( | |
| 428 ERROR_MANIFEST_RESOLVE_URL, | |
| 429 nacl::string("ResolveKey: no such \"files\" entry: ") + key); | |
| 430 *is_portable = false; | |
| 431 return false; | |
| 432 } | |
| 433 nacl::string error_string; | |
| 434 if (!GetKeyUrl(files, rest, sandbox_isa_, | |
| 435 full_url, &error_string, is_portable)) { | |
| 436 error_info->SetReport(ERROR_MANIFEST_RESOLVE_URL, error_string); | |
| 437 *full_url = ""; | |
| 438 return false; | |
| 439 } | |
| 440 return true; | |
| 441 } | |
| 442 | |
| 443 // TODO(jvoung): We won't need these if we figure out how to install llc and ld. | |
| 444 bool Manifest::GetLLCURL(nacl::string* full_url, ErrorInfo* error_info) { | |
| 445 if (full_url == NULL || error_info == NULL) | |
| 446 return false; | |
| 447 | |
| 448 Json::Value pnacl_llc = dictionary_[kPnaclLlcKey]; | |
| 449 | |
| 450 nacl::string nexe_url; | |
| 451 nacl::string error_string; | |
| 452 bool is_portable; | |
| 453 if (!GetURLFromISADictionary(pnacl_llc, | |
| 454 sandbox_isa_, | |
| 455 &nexe_url, | |
| 456 &error_string, | |
| 457 &is_portable)) { | |
| 458 error_info->SetReport(ERROR_MANIFEST_GET_NEXE_URL, | |
| 459 nacl::string(kPnaclLlcKey) + ":" + sandbox_isa_ + | |
| 460 error_string); | |
| 461 return false; | |
| 462 } | |
| 463 | |
| 464 if (is_portable) { | |
| 465 // Bootstrap problem -- we need this to translate portable programs! | |
| 466 error_info->SetReport(ERROR_MANIFEST_GET_NEXE_URL, | |
| 467 nacl::string(kPnaclLlcKey) + | |
| 468 " must be pre-translated for " + sandbox_isa_ + "!"); | |
| 469 return false; | |
| 470 } | |
| 471 | |
| 472 return ResolveURL(nexe_url, full_url, error_info); | |
| 473 } | |
| 474 | |
| 475 bool Manifest::GetLDURL(nacl::string* full_url, ErrorInfo* error_info) { | |
| 476 if (full_url == NULL || error_info == NULL) | |
| 477 return false; | |
| 478 | |
| 479 Json::Value pnacl_ld = dictionary_[kPnaclLdKey]; | |
| 480 | |
| 481 nacl::string nexe_url; | |
| 482 nacl::string error_string; | |
| 483 bool is_portable; | |
| 484 if (!GetURLFromISADictionary(pnacl_ld, | |
| 485 sandbox_isa_, | |
| 486 &nexe_url, | |
| 487 &error_string, | |
| 488 &is_portable)) { | |
| 489 error_info->SetReport(ERROR_MANIFEST_GET_NEXE_URL, | |
| 490 nacl::string(kPnaclLdKey) + ":" + sandbox_isa_ + | |
| 491 error_string); | |
| 492 return false; | |
| 493 } | |
| 494 | |
| 495 if (is_portable) { | |
| 496 // Bootstrap problem -- we need this to translate portable programs! | |
| 497 error_info->SetReport(ERROR_MANIFEST_GET_NEXE_URL, | |
| 498 nacl::string(kPnaclLdKey) + | |
| 499 " must be pre-translated for " + sandbox_isa_ + "!"); | |
| 500 return false; | |
| 501 } | |
| 502 | |
| 503 return ResolveURL(nexe_url, full_url, error_info); | |
| 504 } | |
| 505 | |
| 506 } // namespace plugin | |
| OLD | NEW |