1 // Copyright 2014 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.
5 #include "components/nacl/renderer/json_manifest.h"
9 #include "base/logging.h"
10 #include "base/macros.h"
11 #include "components/nacl/renderer/nexe_load_manager.h"
12 #include "third_party/jsoncpp/source/include/json/reader.h"
13 #include "third_party/jsoncpp/source/include/json/value.h"
19 // Top-level section name keys
20 const char* const kProgramKey
= "program";
21 const char* const kInterpreterKey
= "interpreter";
22 const char* const kFilesKey
= "files";
24 // ISA Dictionary keys
25 const char* const kX8632Key
= "x86-32";
26 const char* const kX8632NonSFIKey
= "x86-32-nonsfi";
27 const char* const kX8664Key
= "x86-64";
28 const char* const kX8664NonSFIKey
= "x86-64-nonsfi";
29 const char* const kArmKey
= "arm";
30 const char* const kArmNonSFIKey
= "arm-nonsfi";
31 const char* const kPortableKey
= "portable";
33 // Url Resolution keys
34 const char* const kPnaclDebugKey
= "pnacl-debug";
35 const char* const kPnaclTranslateKey
= "pnacl-translate";
36 const char* const kUrlKey
= "url";
39 const char* const kOptLevelKey
= "optlevel";
41 // Sample NaCl manifest file:
44 // "x86-32": {"url": "myprogram_x86-32.nexe"},
45 // "x86-64": {"url": "myprogram_x86-64.nexe"},
46 // "arm": {"url": "myprogram_arm.nexe"}
49 // "x86-32": {"url": "interpreter_x86-32.nexe"},
50 // "x86-64": {"url": "interpreter_x86-64.nexe"},
51 // "arm": {"url": "interpreter_arm.nexe"}
55 // "portable": {"url": "foo.txt"}
58 // "x86-32": {"url": "x86-32/bar.txt"},
59 // "portable": {"url": "bar.txt"}
62 // "x86-64" : { "url": "..." }
67 // Sample PNaCl manifest file:
71 // "pnacl-translate": {
72 // "url": "myprogram.pexe"
75 // "url": "myprogram.debug.pexe",
82 // "portable": {"url": "foo.txt"}
85 // "portable": {"url": "bar.txt"}
90 // Returns the key for the architecture in non-SFI mode.
91 std::string
GetNonSFIKey(const std::string
& sandbox_isa
) {
92 return sandbox_isa
+ "-nonsfi";
95 // Looks up |property_name| in the vector |valid_names| with length
96 // |valid_name_count|. Returns true if |property_name| is found.
97 bool FindMatchingProperty(const std::string
& property_name
,
98 const char** valid_names
,
99 size_t valid_name_count
) {
100 for (size_t i
= 0; i
< valid_name_count
; ++i
) {
101 if (property_name
== valid_names
[i
]) {
108 // Return true if this is a valid dictionary. Having only keys present in
109 // |valid_keys| and having at least the keys in |required_keys|.
110 // Error messages will be placed in |error_string|, given that the dictionary
111 // was the property value of |container_key|.
112 // E.g., "container_key" : dictionary
113 bool IsValidDictionary(const Json::Value
& dictionary
,
114 const std::string
& container_key
,
115 const std::string
& parent_key
,
116 const char** valid_keys
,
117 size_t valid_key_count
,
118 const char** required_keys
,
119 size_t required_key_count
,
120 std::string
* error_string
) {
121 if (!dictionary
.isObject()) {
122 std::stringstream error_stream
;
123 error_stream
<< parent_key
<< " property '" << container_key
124 << "' is non-dictionary value '"
125 << dictionary
.toStyledString() << "'.";
126 *error_string
= error_stream
.str();
129 // Check for unknown dictionary members.
130 Json::Value::Members members
= dictionary
.getMemberNames();
131 for (size_t i
= 0; i
< members
.size(); ++i
) {
132 std::string property_name
= members
[i
];
133 if (!FindMatchingProperty(property_name
,
136 // For forward compatibility, we do not prohibit other keys being in
138 VLOG(1) << "WARNING: '" << parent_key
<< "' property '"
139 << container_key
<< "' has unknown key '"
140 << property_name
<< "'.";
143 // Check for required members.
144 for (size_t i
= 0; i
< required_key_count
; ++i
) {
145 if (!dictionary
.isMember(required_keys
[i
])) {
146 std::stringstream error_stream
;
147 error_stream
<< parent_key
<< " property '" << container_key
148 << "' does not have required key: '"
149 << required_keys
[i
] << "'.";
150 *error_string
= error_stream
.str();
157 // Validate a "url" dictionary assuming it was resolved from container_key.
158 // E.g., "container_key" : { "url": "foo.txt" }
159 bool IsValidUrlSpec(const Json::Value
& url_spec
,
160 const std::string
& container_key
,
161 const std::string
& parent_key
,
162 const std::string
& sandbox_isa
,
163 std::string
* error_string
) {
164 static const char* kManifestUrlSpecRequired
[] = {
167 const char** urlSpecPlusOptional
;
168 size_t urlSpecPlusOptionalLength
;
169 if (sandbox_isa
== kPortableKey
) {
170 static const char* kPnaclUrlSpecPlusOptional
[] = {
174 urlSpecPlusOptional
= kPnaclUrlSpecPlusOptional
;
175 urlSpecPlusOptionalLength
= arraysize(kPnaclUrlSpecPlusOptional
);
177 // URL specifications must not contain "pnacl-translate" keys.
178 // This prohibits NaCl clients from invoking PNaCl.
179 if (url_spec
.isMember(kPnaclTranslateKey
)) {
180 std::stringstream error_stream
;
181 error_stream
<< "PNaCl-like NMF with application/x-nacl mimetype instead "
182 << "of x-pnacl mimetype (has " << kPnaclTranslateKey
<< ").";
183 *error_string
= error_stream
.str();
186 urlSpecPlusOptional
= kManifestUrlSpecRequired
;
187 urlSpecPlusOptionalLength
= arraysize(kManifestUrlSpecRequired
);
189 if (!IsValidDictionary(url_spec
, container_key
, parent_key
,
191 urlSpecPlusOptionalLength
,
192 kManifestUrlSpecRequired
,
193 arraysize(kManifestUrlSpecRequired
),
197 // Verify the correct types of the fields if they exist.
198 Json::Value url
= url_spec
[kUrlKey
];
199 if (!url
.isString()) {
200 std::stringstream error_stream
;
201 error_stream
<< parent_key
<< " property '" << container_key
<<
202 "' has non-string value '" << url
.toStyledString() <<
203 "' for key '" << kUrlKey
<< "'.";
204 *error_string
= error_stream
.str();
207 Json::Value opt_level
= url_spec
[kOptLevelKey
];
208 if (!opt_level
.empty() && !opt_level
.isNumeric()) {
209 std::stringstream error_stream
;
210 error_stream
<< parent_key
<< " property '" << container_key
<<
211 "' has non-numeric value '" << opt_level
.toStyledString() <<
212 "' for key '" << kOptLevelKey
<< "'.";
213 *error_string
= error_stream
.str();
219 // Validate a "pnacl-translate" or "pnacl-debug" dictionary, assuming
220 // it was resolved from container_key.
221 // E.g., "container_key" : { "pnacl-translate" : URLSpec }
222 bool IsValidPnaclTranslateSpec(const Json::Value
& pnacl_spec
,
223 const std::string
& container_key
,
224 const std::string
& parent_key
,
225 const std::string
& sandbox_isa
,
226 std::string
* error_string
) {
227 static const char* kManifestPnaclSpecValid
[] = {
231 static const char* kManifestPnaclSpecRequired
[] = { kPnaclTranslateKey
};
232 if (!IsValidDictionary(pnacl_spec
, container_key
, parent_key
,
233 kManifestPnaclSpecValid
,
234 arraysize(kManifestPnaclSpecValid
),
235 kManifestPnaclSpecRequired
,
236 arraysize(kManifestPnaclSpecRequired
),
240 Json::Value url_spec
= pnacl_spec
[kPnaclTranslateKey
];
241 return IsValidUrlSpec(url_spec
, kPnaclTranslateKey
,
242 container_key
, sandbox_isa
, error_string
);
245 // Validates that |dictionary| is a valid ISA dictionary. An ISA dictionary
246 // is validated to have keys from within the set of recognized ISAs. Unknown
247 // ISAs are allowed, but ignored and warnings are produced. It is also
249 // that it must have an entry to match the ISA specified in |sandbox_isa| or
250 // have a fallback 'portable' entry if there is no match. Returns true if
251 // |dictionary| is an ISA to URL map. Sets |error_info| to something
252 // descriptive if it fails.
253 bool IsValidISADictionary(const Json::Value
& dictionary
,
254 const std::string
& parent_key
,
255 const std::string
& sandbox_isa
,
256 bool must_find_matching_entry
,
258 JsonManifest::ErrorInfo
* error_info
) {
259 // An ISA to URL dictionary has to be an object.
260 if (!dictionary
.isObject()) {
261 error_info
->error
= PP_NACL_ERROR_MANIFEST_SCHEMA_VALIDATE
;
262 error_info
->string
= std::string("manifest: ") + parent_key
+
263 " property is not an ISA to URL dictionary";
266 // Build the set of reserved ISA dictionary keys.
267 const char** isaProperties
;
268 size_t isaPropertiesLength
;
269 if (sandbox_isa
== kPortableKey
) {
270 // The known values for PNaCl ISA dictionaries in the manifest.
271 static const char* kPnaclManifestISAProperties
[] = {
274 isaProperties
= kPnaclManifestISAProperties
;
275 isaPropertiesLength
= arraysize(kPnaclManifestISAProperties
);
277 // The known values for NaCl ISA dictionaries in the manifest.
278 static const char* kNaClManifestISAProperties
[] = {
285 // "portable" is here to allow checking that, if present, it can
286 // only refer to an URL, such as for a data file, and not to
287 // "pnacl-translate", which would cause the creation of a nexe.
290 isaProperties
= kNaClManifestISAProperties
;
291 isaPropertiesLength
= arraysize(kNaClManifestISAProperties
);
293 // Check that entries in the dictionary are structurally correct.
294 Json::Value::Members members
= dictionary
.getMemberNames();
295 for (size_t i
= 0; i
< members
.size(); ++i
) {
296 std::string property_name
= members
[i
];
297 Json::Value property_value
= dictionary
[property_name
];
298 std::string error_string
;
299 if (FindMatchingProperty(property_name
,
301 isaPropertiesLength
)) {
302 // For NaCl, arch entries can only be
303 // "arch/portable" : URLSpec
304 // For PNaCl arch in "program" dictionary entries can be
305 // "portable" : { "pnacl-translate": URLSpec }
306 // or "portable" : { "pnacl-debug": URLSpec }
307 // For PNaCl arch elsewhere, dictionary entries can only be
308 // "portable" : URLSpec
309 if ((sandbox_isa
!= kPortableKey
&&
310 !IsValidUrlSpec(property_value
, property_name
, parent_key
,
311 sandbox_isa
, &error_string
)) ||
312 (sandbox_isa
== kPortableKey
&&
313 parent_key
== kProgramKey
&&
314 !IsValidPnaclTranslateSpec(property_value
, property_name
, parent_key
,
315 sandbox_isa
, &error_string
)) ||
316 (sandbox_isa
== kPortableKey
&&
317 parent_key
!= kProgramKey
&&
318 !IsValidUrlSpec(property_value
, property_name
, parent_key
,
319 sandbox_isa
, &error_string
))) {
320 error_info
->error
= PP_NACL_ERROR_MANIFEST_SCHEMA_VALIDATE
;
321 error_info
->string
= "manifest: " + error_string
;
325 // For forward compatibility, we do not prohibit other keys being in
326 // the dictionary, as they may be architectures supported in later
327 // versions. However, the value of these entries must be an URLSpec.
328 VLOG(1) << "IsValidISADictionary: unrecognized key '"
329 << property_name
<< "'.";
330 if (!IsValidUrlSpec(property_value
, property_name
, parent_key
,
331 sandbox_isa
, &error_string
)) {
332 error_info
->error
= PP_NACL_ERROR_MANIFEST_SCHEMA_VALIDATE
;
333 error_info
->string
= "manifest: " + error_string
;
339 if (sandbox_isa
== kPortableKey
) {
340 if (!dictionary
.isMember(kPortableKey
)) {
341 error_info
->error
= PP_NACL_ERROR_MANIFEST_PROGRAM_MISSING_ARCH
;
342 error_info
->string
= "manifest: no version of " + parent_key
+
343 " given for portable.";
346 } else if (must_find_matching_entry
) {
347 // TODO(elijahtaylor) add ISA resolver here if we expand ISAs to include
348 // micro-architectures that can resolve to multiple valid sandboxes.
349 bool has_isa
= dictionary
.isMember(sandbox_isa
);
350 bool has_nonsfi_isa
=
351 nonsfi_enabled
&& dictionary
.isMember(GetNonSFIKey(sandbox_isa
));
352 bool has_portable
= dictionary
.isMember(kPortableKey
);
354 if (!has_isa
&& !has_nonsfi_isa
&& !has_portable
) {
355 error_info
->error
= PP_NACL_ERROR_MANIFEST_PROGRAM_MISSING_ARCH
;
356 error_info
->string
= "manifest: no version of " + parent_key
+
357 " given for current arch and no portable version found.";
364 void GrabUrlAndPnaclOptions(const Json::Value
& url_spec
,
366 PP_PNaClOptions
* pnacl_options
) {
367 *url
= url_spec
[kUrlKey
].asString();
368 pnacl_options
->translate
= PP_TRUE
;
369 if (url_spec
.isMember(kOptLevelKey
)) {
370 int32_t opt_raw
= url_spec
[kOptLevelKey
].asInt();
371 // Currently only allow 0 or 2, since that is what we test.
373 pnacl_options
->opt_level
= 0;
375 pnacl_options
->opt_level
= 2;
381 JsonManifest::JsonManifest(const std::string
& manifest_base_url
,
382 const std::string
& sandbox_isa
,
385 : manifest_base_url_(manifest_base_url
),
386 sandbox_isa_(sandbox_isa
),
387 nonsfi_enabled_(nonsfi_enabled
),
388 pnacl_debug_(pnacl_debug
) { }
390 bool JsonManifest::Init(const std::string
& manifest_json
,
391 ErrorInfo
* error_info
) {
395 if (!reader
.parse(manifest_json
, dictionary_
)) {
396 std::string json_error
= reader
.getFormattedErrorMessages();
397 error_info
->error
= PP_NACL_ERROR_MANIFEST_PARSING
;
398 error_info
->string
= "manifest JSON parsing failed: " + json_error
;
401 // Parse has ensured the string was valid JSON. Check that it matches the
403 return MatchesSchema(error_info
);
406 bool JsonManifest::GetProgramURL(std::string
* full_url
,
407 PP_PNaClOptions
* pnacl_options
,
408 bool* uses_nonsfi_mode
,
409 ErrorInfo
* error_info
) const {
412 CHECK(pnacl_options
);
413 CHECK(uses_nonsfi_mode
);
416 const Json::Value
& program
= dictionary_
[kProgramKey
];
417 std::string nexe_url
;
418 if (!GetURLFromISADictionary(program
,
427 // The contents of the manifest are resolved relative to the manifest URL.
428 GURL
base_gurl(manifest_base_url_
);
429 if (!base_gurl
.is_valid())
432 GURL resolved_gurl
= base_gurl
.Resolve(nexe_url
);
433 if (!resolved_gurl
.is_valid()) {
434 error_info
->error
= PP_NACL_ERROR_MANIFEST_RESOLVE_URL
;
436 "could not resolve url '" + nexe_url
+
437 "' relative to manifest base url '" + manifest_base_url_
.c_str() +
441 *full_url
= resolved_gurl
.possibly_invalid_spec();
445 void JsonManifest::GetPrefetchableFiles(
446 std::vector
<std::pair
<std::string
, std::string
> >* out_files
) const {
447 const Json::Value
& files
= dictionary_
[kFilesKey
];
448 if (!files
.isObject())
451 Json::Value::Members keys
= files
.getMemberNames();
452 for (size_t i
= 0; i
< keys
.size(); ++i
) {
453 std::string full_url
;
454 PP_PNaClOptions unused_pnacl_options
; // pnacl does not support "files".
455 // We skip invalid entries in "files".
456 if (GetKeyUrl(files
, keys
[i
], &full_url
, &unused_pnacl_options
)) {
457 if (GURL(full_url
).SchemeIs("chrome-extension"))
458 out_files
->push_back(std::make_pair(keys
[i
], full_url
));
463 bool JsonManifest::ResolveKey(const std::string
& key
,
464 std::string
* full_url
,
465 PP_PNaClOptions
* pnacl_options
) const {
466 // key must be one of kProgramKey or kFileKey '/' file-section-key
467 if (full_url
== NULL
|| pnacl_options
== NULL
)
470 if (key
== kProgramKey
)
471 return GetKeyUrl(dictionary_
, key
, full_url
, pnacl_options
);
473 std::string::const_iterator p
= std::find(key
.begin(), key
.end(), '/');
474 if (p
== key
.end()) {
475 VLOG(1) << "ResolveKey failed: invalid key, no slash: " << key
;
479 // generalize to permit other sections?
480 std::string
prefix(key
.begin(), p
);
481 if (prefix
!= kFilesKey
) {
482 VLOG(1) << "ResolveKey failed: invalid key, no \"files\" prefix: " << key
;
486 const Json::Value
& files
= dictionary_
[kFilesKey
];
487 if (!files
.isObject()) {
488 VLOG(1) << "ResolveKey failed: no \"files\" dictionary";
492 std::string
rest(p
+ 1, key
.end());
493 if (!files
.isMember(rest
)) {
494 VLOG(1) << "ResolveKey failed: no such \"files\" entry: " << key
;
497 return GetKeyUrl(files
, rest
, full_url
, pnacl_options
);
500 bool JsonManifest::MatchesSchema(ErrorInfo
* error_info
) {
501 if (!dictionary_
.isObject()) {
502 error_info
->error
= PP_NACL_ERROR_MANIFEST_SCHEMA_VALIDATE
;
503 error_info
->string
= "manifest: is not a json dictionary.";
506 Json::Value::Members members
= dictionary_
.getMemberNames();
507 for (size_t i
= 0; i
< members
.size(); ++i
) {
508 // The top level dictionary entries valid in the manifest file.
509 static const char* kManifestTopLevelProperties
[] = { kProgramKey
,
512 std::string property_name
= members
[i
];
513 if (!FindMatchingProperty(property_name
,
514 kManifestTopLevelProperties
,
515 arraysize(kManifestTopLevelProperties
))) {
516 VLOG(1) << "JsonManifest::MatchesSchema: WARNING: unknown top-level "
517 << "section '" << property_name
<< "' in manifest.";
521 // A manifest file must have a program section.
522 if (!dictionary_
.isMember(kProgramKey
)) {
523 error_info
->error
= PP_NACL_ERROR_MANIFEST_SCHEMA_VALIDATE
;
524 error_info
->string
= std::string("manifest: missing '") + kProgramKey
+
529 // Validate the program section.
530 // There must be a matching (portable or sandbox_isa_) entry for program for
532 if (!IsValidISADictionary(dictionary_
[kProgramKey
],
541 // Validate the interpreter section (if given).
542 // There must be a matching (portable or sandbox_isa_) entry for interpreter
544 if (dictionary_
.isMember(kInterpreterKey
)) {
545 if (!IsValidISADictionary(dictionary_
[kInterpreterKey
],
555 // Validate the file dictionary (if given).
556 // The "files" key does not require a matching (portable or sandbox_isa_)
557 // entry at schema validation time for NaCl. This allows manifests to
558 // specify resources that are only loaded for a particular sandbox_isa.
559 if (dictionary_
.isMember(kFilesKey
)) {
560 const Json::Value
& files
= dictionary_
[kFilesKey
];
561 if (!files
.isObject()) {
562 error_info
->error
= PP_NACL_ERROR_MANIFEST_SCHEMA_VALIDATE
;
563 error_info
->string
= std::string("manifest: '") + kFilesKey
+
564 "' is not a dictionary.";
566 Json::Value::Members members
= files
.getMemberNames();
567 for (size_t i
= 0; i
< members
.size(); ++i
) {
568 std::string file_name
= members
[i
];
569 if (!IsValidISADictionary(files
[file_name
],
582 bool JsonManifest::GetKeyUrl(const Json::Value
& dictionary
,
583 const std::string
& key
,
584 std::string
* full_url
,
585 PP_PNaClOptions
* pnacl_options
) const {
586 DCHECK(full_url
&& pnacl_options
);
587 if (!dictionary
.isMember(key
)) {
588 VLOG(1) << "GetKeyUrl failed: file " << key
<< " not found in manifest.";
591 const Json::Value
& isa_dict
= dictionary
[key
];
592 std::string relative_url
;
593 bool uses_nonsfi_mode
;
594 ErrorInfo ignored_error_info
;
595 if (!GetURLFromISADictionary(isa_dict
, key
, &relative_url
,
596 pnacl_options
, &uses_nonsfi_mode
,
597 &ignored_error_info
))
600 // The contents of the manifest are resolved relative to the manifest URL.
601 GURL
base_gurl(manifest_base_url_
);
602 if (!base_gurl
.is_valid())
604 GURL resolved_gurl
= base_gurl
.Resolve(relative_url
);
605 if (!resolved_gurl
.is_valid())
607 *full_url
= resolved_gurl
.possibly_invalid_spec();
611 bool JsonManifest::GetURLFromISADictionary(const Json::Value
& dictionary
,
612 const std::string
& parent_key
,
614 PP_PNaClOptions
* pnacl_options
,
615 bool* uses_nonsfi_mode
,
616 ErrorInfo
* error_info
) const {
617 DCHECK(url
&& pnacl_options
&& error_info
);
619 // When the application actually requests a resolved URL, we must have
620 // a matching entry (sandbox_isa_ or portable) for NaCl.
621 ErrorInfo ignored_error_info
;
622 if (!IsValidISADictionary(dictionary
, parent_key
, sandbox_isa_
, true,
623 nonsfi_enabled_
, &ignored_error_info
)) {
624 error_info
->error
= PP_NACL_ERROR_MANIFEST_RESOLVE_URL
;
625 error_info
->string
= "architecture " + sandbox_isa_
+
626 " is not found for file " + parent_key
;
630 // The call to IsValidISADictionary() above guarantees that either
631 // sandbox_isa_, its nonsfi mode, or kPortableKey is present in the
633 *uses_nonsfi_mode
= false;
634 std::string chosen_isa
;
635 if (sandbox_isa_
== kPortableKey
) {
636 chosen_isa
= kPortableKey
;
638 std::string nonsfi_isa
= GetNonSFIKey(sandbox_isa_
);
639 if (nonsfi_enabled_
&& dictionary
.isMember(nonsfi_isa
)) {
640 chosen_isa
= nonsfi_isa
;
641 *uses_nonsfi_mode
= true;
642 } else if (dictionary
.isMember(sandbox_isa_
)) {
643 chosen_isa
= sandbox_isa_
;
644 } else if (dictionary
.isMember(kPortableKey
)) {
645 chosen_isa
= kPortableKey
;
647 // Should not reach here, because the earlier IsValidISADictionary()
648 // call checked that the manifest covers the current architecture.
654 const Json::Value
& isa_spec
= dictionary
[chosen_isa
];
655 // If the PNaCl debug flag is turned on, look for pnacl-debug entries first.
656 // If found, mark that it is a debug URL. Otherwise, fall back to
657 // checking for pnacl-translate URLs, etc. and don't mark it as a debug URL.
658 if (pnacl_debug_
&& isa_spec
.isMember(kPnaclDebugKey
)) {
659 GrabUrlAndPnaclOptions(isa_spec
[kPnaclDebugKey
], url
, pnacl_options
);
660 pnacl_options
->is_debug
= PP_TRUE
;
661 } else if (isa_spec
.isMember(kPnaclTranslateKey
)) {
662 GrabUrlAndPnaclOptions(isa_spec
[kPnaclTranslateKey
], url
, pnacl_options
);
665 *url
= isa_spec
[kUrlKey
].asString();
666 pnacl_options
->translate
= PP_FALSE
;