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 |