Index: ppapi/native_client/src/trusted/plugin/json_manifest.cc |
diff --git a/ppapi/native_client/src/trusted/plugin/json_manifest.cc b/ppapi/native_client/src/trusted/plugin/json_manifest.cc |
index d1f70ff39f96e278f197b6151e01f34c58b7818b..1aa8d23a15a5248788fa2e2fec2224f48c3e12fa 100644 |
--- a/ppapi/native_client/src/trusted/plugin/json_manifest.cc |
+++ b/ppapi/native_client/src/trusted/plugin/json_manifest.cc |
@@ -45,19 +45,12 @@ const char* const kCacheIdentityKey = "sha256"; |
const char* const kOptLevelKey = "-O"; |
const char* const kPnaclExperimentalFlags = "experimental_flags"; |
-// Sample manifest file: |
+// Sample NaCL manifest file: |
// { |
// "program": { |
// "x86-32": {"url": "myprogram_x86-32.nexe"}, |
// "x86-64": {"url": "myprogram_x86-64.nexe"}, |
-// "arm": {"url": "myprogram_arm.nexe"}, |
-// "portable": { |
-// "pnacl-translate": { |
-// "url": "myprogram.pexe", |
-// "sha256": "...", |
-// "-O": 0 |
-// } |
-// } |
+// "arm": {"url": "myprogram_arm.nexe"} |
// }, |
// "interpreter": { |
// "x86-32": {"url": "interpreter_x86-32.nexe"}, |
@@ -73,10 +66,28 @@ const char* const kPnaclExperimentalFlags = "experimental_flags"; |
// "portable": {"url": "bar.txt"} |
// }, |
// "libfoo.so": { |
-// "x86-64-ivybridge-foo": { "url": "..."}, |
-// "x86-64-ivybridge" : { "pnacl-translate": { "url": "..."}}, |
-// "x86-64" : { "url": "..." }, |
-// "portable": {"pnacl-translate": {"url": "..."}} |
+// "x86-64" : { "url": "..." } |
+// } |
+// } |
+// } |
+ |
+// Sample PNaCl manifest file: |
+// { |
+// "program": { |
+// "portable": { |
+// "pnacl-translate": { |
+// "url": "myprogram.pexe", |
+// "sha256": "...", |
+// "-O": 0 |
+// } |
+// } |
+// }, |
+// "files": { |
+// "foo.txt": { |
+// "portable": {"url": "foo.txt"} |
+// }, |
+// "bar.txt": { |
+// "portable": {"url": "bar.txt"} |
// } |
// } |
// } |
@@ -122,7 +133,8 @@ bool IsValidDictionary(const Json::Value& dictionary, |
if (!FindMatchingProperty(property_name, |
valid_keys, |
valid_key_count)) { |
- // TODO(jvoung): Should this set error_string and return false? |
+ // For forward compatibility, we do not prohibit other keys being in |
+ // the dictionary. |
PLUGIN_PRINTF(("WARNING: '%s' property '%s' has unknown key '%s'.\n", |
parent_key.c_str(), |
container_key.c_str(), property_name.c_str())); |
@@ -147,22 +159,44 @@ bool IsValidDictionary(const Json::Value& dictionary, |
bool IsValidUrlSpec(const Json::Value& url_spec, |
const nacl::string& container_key, |
const nacl::string& parent_key, |
+ const nacl::string& sandbox_isa, |
nacl::string* error_string) { |
static const char* kManifestUrlSpecRequired[] = { |
kUrlKey |
}; |
- static const char* kManifestUrlSpecPlusOptional[] = { |
- kUrlKey, |
- kCacheIdentityKey |
- }; |
+ const char** urlSpecPlusOptional; |
+ size_t urlSpecPlusOptionalLength; |
+ if (sandbox_isa == kPortableKey) { |
+ static const char* kPnaclUrlSpecPlusOptional[] = { |
+ kUrlKey, |
+ kCacheIdentityKey, |
+ kOptLevelKey, |
+ }; |
+ urlSpecPlusOptional = kPnaclUrlSpecPlusOptional; |
+ urlSpecPlusOptionalLength = NACL_ARRAY_SIZE(kPnaclUrlSpecPlusOptional); |
+ } else { |
+ urlSpecPlusOptional = kManifestUrlSpecRequired; |
+ urlSpecPlusOptionalLength = NACL_ARRAY_SIZE(kManifestUrlSpecRequired); |
+ } |
if (!IsValidDictionary(url_spec, container_key, parent_key, |
- kManifestUrlSpecPlusOptional, |
- NACL_ARRAY_SIZE(kManifestUrlSpecPlusOptional), |
+ urlSpecPlusOptional, |
+ urlSpecPlusOptionalLength, |
kManifestUrlSpecRequired, |
NACL_ARRAY_SIZE(kManifestUrlSpecRequired), |
error_string)) { |
return false; |
} |
+ // URL specifications must not contain "pnacl-translate" keys. |
+ // This prohibits NaCl clients from invoking PNaCl. |
+ Json::Value translate = url_spec[kPnaclTranslateKey]; |
+ if (!translate.empty()) { |
+ nacl::stringstream error_stream; |
+ error_stream << parent_key << " property '" << container_key << |
+ "' has '" << kPnaclTranslateKey << "' inside URL spec."; |
+ *error_string = error_stream.str(); |
+ return false; |
+ } |
+ // Verify the correct types of the fields if they exist. |
Json::Value url = url_spec[kUrlKey]; |
if (!url.isString()) { |
nacl::stringstream error_stream; |
@@ -172,6 +206,24 @@ bool IsValidUrlSpec(const Json::Value& url_spec, |
*error_string = error_stream.str(); |
return false; |
} |
+ Json::Value cache_identity = url_spec[kCacheIdentityKey]; |
+ if (!cache_identity.empty() && !cache_identity.isString()) { |
+ nacl::stringstream error_stream; |
+ error_stream << parent_key << " property '" << container_key << |
+ "' has non-string value '" << cache_identity.toStyledString() << |
+ "' for key '" << kCacheIdentityKey << "'."; |
+ *error_string = error_stream.str(); |
+ return false; |
+ } |
+ Json::Value opt_level = url_spec[kOptLevelKey]; |
+ if (!opt_level.empty() && !opt_level.isNumeric()) { |
+ nacl::stringstream error_stream; |
+ error_stream << parent_key << " property '" << container_key << |
+ "' has non-numeric value '" << opt_level.toStyledString() << |
+ "' for key '" << kOptLevelKey << "'."; |
+ *error_string = error_stream.str(); |
+ return false; |
+ } |
return true; |
} |
@@ -180,6 +232,7 @@ bool IsValidUrlSpec(const Json::Value& url_spec, |
bool IsValidPnaclTranslateSpec(const Json::Value& pnacl_spec, |
const nacl::string& container_key, |
const nacl::string& parent_key, |
+ const nacl::string& sandbox_isa, |
nacl::string* error_string) { |
static const char* kManifestPnaclSpecProperties[] = { |
kPnaclTranslateKey |
@@ -194,7 +247,7 @@ bool IsValidPnaclTranslateSpec(const Json::Value& pnacl_spec, |
} |
Json::Value url_spec = pnacl_spec[kPnaclTranslateKey]; |
if (!IsValidUrlSpec(url_spec, kPnaclTranslateKey, |
- container_key, error_string)) { |
+ container_key, sandbox_isa, error_string)) { |
return false; |
} |
return true; |
@@ -210,6 +263,7 @@ bool IsValidPnaclTranslateSpec(const Json::Value& pnacl_spec, |
bool IsValidISADictionary(const Json::Value& dictionary, |
const nacl::string& parent_key, |
const nacl::string& sandbox_isa, |
+ bool must_find_matching_entry, |
ErrorInfo* error_info) { |
if (error_info == NULL) return false; |
@@ -220,39 +274,86 @@ bool IsValidISADictionary(const Json::Value& dictionary, |
" property is not an ISA to URL dictionary"); |
return false; |
} |
- // The keys to the dictionary have to be valid ISA names. |
- Json::Value::Members members = dictionary.getMemberNames(); |
- for (size_t i = 0; i < members.size(); ++i) { |
- // The known ISA values for ISA dictionaries in the manifest. |
- static const char* kManifestISAProperties[] = { |
+ // Build the set of reserved ISA dictionary keys. |
+ const char** isaProperties; |
+ size_t isaPropertiesLength; |
+ if (sandbox_isa == kPortableKey) { |
+ // The known values for PNaCl ISA dictionaries in the manifest. |
+ static const char* kPnaclManifestISAProperties[] = { |
+ kPortableKey |
+ }; |
+ isaProperties = kPnaclManifestISAProperties; |
+ isaPropertiesLength = NACL_ARRAY_SIZE(kPnaclManifestISAProperties); |
+ } else { |
+ // The known values for NaCl ISA dictionaries in the manifest. |
+ static const char* kNaClManifestISAProperties[] = { |
kX8632Key, |
kX8664Key, |
kArmKey, |
+ // "portable" is here to allow checking that, if present, it can |
+ // only refer to an URL, such as for a data file, and not to |
+ // "pnacl-translate", which would cause the creation of a nexe. |
kPortableKey |
}; |
+ isaProperties = kNaClManifestISAProperties; |
+ isaPropertiesLength = NACL_ARRAY_SIZE(kNaClManifestISAProperties); |
+ } |
+ // Check that entries in the dictionary are structurally correct. |
+ Json::Value::Members members = dictionary.getMemberNames(); |
+ for (size_t i = 0; i < members.size(); ++i) { |
nacl::string property_name = members[i]; |
- if (!FindMatchingProperty(property_name, |
- kManifestISAProperties, |
- NACL_ARRAY_SIZE(kManifestISAProperties))) { |
- PLUGIN_PRINTF(("IsValidISADictionary: unrecognized ISA '%s'.\n", |
- property_name.c_str())); |
- } |
- // Could be "arch/portable" : URLSpec, or |
- // it could be "arch/portable" : { "pnacl-translate": URLSpec } |
- // for executables that need to be translated. |
Json::Value property_value = dictionary[property_name]; |
nacl::string error_string; |
- if (!IsValidUrlSpec(property_value, property_name, parent_key, |
- &error_string) && |
- !IsValidPnaclTranslateSpec(property_value, property_name, |
- parent_key, &error_string)) { |
- error_info->SetReport(ERROR_MANIFEST_SCHEMA_VALIDATE, |
- nacl::string("manifest: ") + error_string); |
- return false; |
+ if (FindMatchingProperty(property_name, |
+ isaProperties, |
+ isaPropertiesLength)) { |
+ // For NaCl, arch entries can only be |
+ // "arch/portable" : URLSpec |
+ // For PNaCl arch in "program" dictionary entries can only be |
+ // "portable" : { "pnacl-translate": URLSpec } |
+ // For PNaCl arch elsewhere, dictionary entries can only be |
+ // "portable" : URLSpec |
+ if ((sandbox_isa != kPortableKey && |
+ !IsValidUrlSpec(property_value, property_name, parent_key, |
+ sandbox_isa, &error_string)) || |
+ (sandbox_isa == kPortableKey && |
+ parent_key == kProgramKey && |
+ !IsValidPnaclTranslateSpec(property_value, property_name, parent_key, |
+ sandbox_isa, &error_string)) || |
+ (sandbox_isa == kPortableKey && |
+ parent_key != kProgramKey && |
+ !IsValidUrlSpec(property_value, property_name, parent_key, |
+ sandbox_isa, &error_string))) { |
+ error_info->SetReport(ERROR_MANIFEST_SCHEMA_VALIDATE, |
+ nacl::string("manifest: ") + error_string); |
+ return false; |
+ } |
+ } else { |
+ // For forward compatibility, we do not prohibit other keys being in |
+ // the dictionary, as they may be architectures supported in later |
+ // versions. However, the value of these entries must be an URLSpec. |
+ PLUGIN_PRINTF(("IsValidISADictionary: unrecognized key '%s'.\n", |
+ property_name.c_str())); |
+ if (!IsValidUrlSpec(property_value, property_name, parent_key, |
+ sandbox_isa, &error_string)) { |
+ error_info->SetReport(ERROR_MANIFEST_SCHEMA_VALIDATE, |
+ nacl::string("manifest: ") + error_string); |
+ return false; |
+ } |
} |
} |
- if (!sandbox_isa.empty()) { |
+ if (sandbox_isa == kPortableKey) { |
+ bool has_portable = dictionary.isMember(kPortableKey); |
+ |
+ if (!has_portable) { |
+ error_info->SetReport( |
+ ERROR_MANIFEST_PROGRAM_MISSING_ARCH, |
+ nacl::string("manifest: no version of ") + parent_key + |
+ " given for portable."); |
+ return false; |
+ } |
+ } else if (must_find_matching_entry) { |
// TODO(elijahtaylor) add ISA resolver here if we expand ISAs to include |
// micro-architectures that can resolve to multiple valid sandboxes. |
bool has_isa = dictionary.isMember(sandbox_isa); |
@@ -293,14 +394,16 @@ void GrabUrlAndPnaclOptions(const Json::Value& url_spec, |
bool GetURLFromISADictionary(const Json::Value& dictionary, |
const nacl::string& parent_key, |
const nacl::string& sandbox_isa, |
- bool prefer_portable, |
nacl::string* url, |
PnaclOptions* pnacl_options, |
ErrorInfo* error_info) { |
if (url == NULL || pnacl_options == NULL || error_info == NULL) |
return false; |
- if (!IsValidISADictionary(dictionary, parent_key, sandbox_isa, error_info)) { |
+ // When the application actually requests a resolved URL, we must have |
+ // a matching entry (sandbox_isa or portable) for NaCl. |
+ if (!IsValidISADictionary(dictionary, parent_key, sandbox_isa, true, |
+ error_info)) { |
error_info->SetReport(ERROR_MANIFEST_RESOLVE_URL, |
"architecture " + sandbox_isa + |
" is not found for file " + parent_key); |
@@ -314,7 +417,7 @@ bool GetURLFromISADictionary(const Json::Value& dictionary, |
bool has_portable = dictionary.isMember(kPortableKey); |
bool has_isa = dictionary.isMember(sandbox_isa); |
nacl::string chosen_isa; |
- if ((has_portable && prefer_portable) || !has_isa) { |
+ if ((sandbox_isa == kPortableKey) || (has_portable && !has_isa)) { |
chosen_isa = kPortableKey; |
} else { |
chosen_isa = sandbox_isa; |
@@ -323,9 +426,11 @@ bool GetURLFromISADictionary(const Json::Value& dictionary, |
// Check if this requires a pnacl-translate, otherwise just grab the URL. |
// We may have pnacl-translate for isa-specific bitcode for CPU tuning. |
if (isa_spec.isMember(kPnaclTranslateKey)) { |
+ // PNaCl |
GrabUrlAndPnaclOptions(isa_spec[kPnaclTranslateKey], url, pnacl_options); |
pnacl_options->set_translate(true); |
} else { |
+ // NaCl |
*url = isa_spec[kUrlKey].asString(); |
pnacl_options->set_translate(false); |
} |
@@ -337,7 +442,6 @@ bool GetKeyUrl(const Json::Value& dictionary, |
const nacl::string& key, |
const nacl::string& sandbox_isa, |
const Manifest* manifest, |
- bool prefer_portable, |
nacl::string* full_url, |
PnaclOptions* pnacl_options, |
ErrorInfo* error_info) { |
@@ -349,8 +453,8 @@ bool GetKeyUrl(const Json::Value& dictionary, |
} |
const Json::Value& isa_dict = dictionary[key]; |
nacl::string relative_url; |
- if (!GetURLFromISADictionary(isa_dict, key, sandbox_isa, prefer_portable, |
- &relative_url, pnacl_options, error_info)) { |
+ if (!GetURLFromISADictionary(isa_dict, key, sandbox_isa, &relative_url, |
+ pnacl_options, error_info)) { |
return false; |
} |
return manifest->ResolveURL(relative_url, full_url, error_info); |
@@ -410,24 +514,33 @@ bool JsonManifest::MatchesSchema(ErrorInfo* error_info) { |
} |
// Validate the program section. |
+ // There must be a matching (portable or sandbox_isa_) entry for program for |
+ // NaCl. |
if (!IsValidISADictionary(dictionary_[kProgramKey], |
kProgramKey, |
sandbox_isa_, |
+ true, |
error_info)) { |
return false; |
} |
// Validate the interpreter section (if given). |
+ // There must be a matching (portable or sandbox_isa_) entry for interpreter |
+ // for NaCl. |
if (dictionary_.isMember(kInterpreterKey)) { |
if (!IsValidISADictionary(dictionary_[kInterpreterKey], |
kInterpreterKey, |
sandbox_isa_, |
+ true, |
error_info)) { |
return false; |
} |
} |
// Validate the file dictionary (if given). |
+ // The "files" key does not require a matching (portable or sandbox_isa_) |
+ // entry at schema validation time for NaCl. This allows manifests to specify |
+ // resources that are only loaded for a particular sandbox_isa. |
if (dictionary_.isMember(kFilesKey)) { |
const Json::Value& files = dictionary_[kFilesKey]; |
if (!files.isObject()) { |
@@ -440,7 +553,8 @@ bool JsonManifest::MatchesSchema(ErrorInfo* error_info) { |
nacl::string file_name = members[i]; |
if (!IsValidISADictionary(files[file_name], |
file_name, |
- nacl::string(), |
+ sandbox_isa_, |
+ false, |
error_info)) { |
return false; |
} |
@@ -484,7 +598,6 @@ bool JsonManifest::GetProgramURL(nacl::string* full_url, |
if (!GetURLFromISADictionary(program, |
kProgramKey, |
sandbox_isa_, |
- prefer_portable_, |
&nexe_url, |
pnacl_options, |
error_info)) { |
@@ -519,8 +632,8 @@ bool JsonManifest::ResolveKey(const nacl::string& key, |
return false; |
if (key == kProgramKey) { |
- return GetKeyUrl(dictionary_, key, sandbox_isa_, this, prefer_portable_, |
- full_url, pnacl_options, error_info); |
+ return GetKeyUrl(dictionary_, key, sandbox_isa_, this, full_url, |
+ pnacl_options, error_info); |
} |
nacl::string::const_iterator p = find(key.begin(), key.end(), '/'); |
if (p == key.end()) { |
@@ -554,8 +667,8 @@ bool JsonManifest::ResolveKey(const nacl::string& key, |
nacl::string("ResolveKey: no such \"files\" entry: ") + key); |
return false; |
} |
- return GetKeyUrl(files, rest, sandbox_isa_, this, prefer_portable_, |
- full_url, pnacl_options, error_info); |
+ return GetKeyUrl(files, rest, sandbox_isa_, this, full_url, pnacl_options, |
+ error_info); |
} |
} // namespace plugin |