普通文本  |  652行  |  23.79 KB

/*
 * Copyright (c) 2012 The Chromium Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include <algorithm>

#include "ppapi/native_client/src/trusted/plugin/json_manifest.h"

#include <stdlib.h>

#include "native_client/src/include/nacl_base.h"
#include "native_client/src/include/nacl_macros.h"
#include "native_client/src/include/nacl_string.h"
#include "native_client/src/include/portability.h"
#include "native_client/src/shared/platform/nacl_check.h"
#include "ppapi/cpp/dev/url_util_dev.h"
#include "ppapi/cpp/var.h"
#include "ppapi/native_client/src/trusted/plugin/plugin_error.h"
#include "ppapi/native_client/src/trusted/plugin/pnacl_options.h"
#include "ppapi/native_client/src/trusted/plugin/utility.h"
#include "third_party/jsoncpp/source/include/json/reader.h"

namespace plugin {

namespace {
// Top-level section name keys
const char* const kProgramKey =     "program";
const char* const kInterpreterKey = "interpreter";
const char* const kFilesKey =       "files";

// ISA Dictionary keys
const char* const kX8632Key =       "x86-32";
const char* const kX8664Key =       "x86-64";
const char* const kArmKey =         "arm";
const char* const kPortableKey =    "portable";

// Url Resolution keys
const char* const kPnaclTranslateKey = "pnacl-translate";
const char* const kUrlKey =            "url";

// PNaCl keys
const char* const kOptLevelKey = "optlevel";

// Sample NaCl manifest file:
// {
//   "program": {
//     "x86-32": {"url": "myprogram_x86-32.nexe"},
//     "x86-64": {"url": "myprogram_x86-64.nexe"},
//     "arm": {"url": "myprogram_arm.nexe"}
//   },
//   "interpreter": {
//     "x86-32": {"url": "interpreter_x86-32.nexe"},
//     "x86-64": {"url": "interpreter_x86-64.nexe"},
//     "arm": {"url": "interpreter_arm.nexe"}
//   },
//   "files": {
//     "foo.txt": {
//       "portable": {"url": "foo.txt"}
//     },
//     "bar.txt": {
//       "x86-32": {"url": "x86-32/bar.txt"},
//       "portable": {"url": "bar.txt"}
//     },
//     "libfoo.so": {
//       "x86-64" : { "url": "..." }
//     }
//   }
// }

// Sample PNaCl manifest file:
// {
//   "program": {
//     "portable": {
//       "pnacl-translate": {
//         "url": "myprogram.pexe",
//         "optlevel": 0
//       }
//     }
//   },
//   "files": {
//     "foo.txt": {
//       "portable": {"url": "foo.txt"}
//     },
//     "bar.txt": {
//       "portable": {"url": "bar.txt"}
//     }
//   }
// }

// Looks up |property_name| in the vector |valid_names| with length
// |valid_name_count|.  Returns true if |property_name| is found.
bool FindMatchingProperty(const nacl::string& property_name,
                          const char** valid_names,
                          size_t valid_name_count) {
  for (size_t i = 0; i < valid_name_count; ++i) {
    if (property_name == valid_names[i]) {
      return true;
    }
  }
  return false;
}

// Return true if this is a valid dictionary.  Having only keys present in
// |valid_keys| and having at least the keys in |required_keys|.
// Error messages will be placed in |error_string|, given that the dictionary
// was the property value of |container_key|.
// E.g., "container_key" : dictionary
bool IsValidDictionary(const Json::Value& dictionary,
                       const nacl::string& container_key,
                       const nacl::string& parent_key,
                       const char** valid_keys,
                       size_t valid_key_count,
                       const char** required_keys,
                       size_t required_key_count,
                       nacl::string* error_string) {
  if (!dictionary.isObject()) {
    nacl::stringstream error_stream;
    error_stream << parent_key << " property '" << container_key
                 << "' is non-dictionary value '"
                 << dictionary.toStyledString() << "'.";
    *error_string = error_stream.str();
    return false;
  }
  // Check for unknown dictionary members.
  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,
                              valid_keys,
                              valid_key_count)) {
      // 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()));
    }
  }
  // Check for required members.
  for (size_t i = 0; i < required_key_count; ++i) {
    if (!dictionary.isMember(required_keys[i])) {
      nacl::stringstream error_stream;
      error_stream << parent_key << " property '" << container_key
                   << "' does not have required key: '"
                   << required_keys[i] << "'.";
      *error_string = error_stream.str();
      return false;
    }
  }
  return true;
}

// Validate a "url" dictionary assuming it was resolved from container_key.
// E.g., "container_key" : { "url": "foo.txt" }
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
  };
  const char** urlSpecPlusOptional;
  size_t urlSpecPlusOptionalLength;
  if (sandbox_isa == kPortableKey) {
    static const char* kPnaclUrlSpecPlusOptional[] = {
      kUrlKey,
      kOptLevelKey,
    };
    urlSpecPlusOptional = kPnaclUrlSpecPlusOptional;
    urlSpecPlusOptionalLength = NACL_ARRAY_SIZE(kPnaclUrlSpecPlusOptional);
  } else {
    // URL specifications must not contain "pnacl-translate" keys.
    // This prohibits NaCl clients from invoking PNaCl.
    if (url_spec.isMember(kPnaclTranslateKey)) {
      nacl::stringstream error_stream;
      error_stream << "PNaCl-like NMF with application/x-nacl mimetype instead "
                   << "of x-pnacl mimetype (has " << kPnaclTranslateKey << ").";
      *error_string = error_stream.str();
      return false;
    }
    urlSpecPlusOptional = kManifestUrlSpecRequired;
    urlSpecPlusOptionalLength = NACL_ARRAY_SIZE(kManifestUrlSpecRequired);
  }
  if (!IsValidDictionary(url_spec, container_key, parent_key,
                         urlSpecPlusOptional,
                         urlSpecPlusOptionalLength,
                         kManifestUrlSpecRequired,
                         NACL_ARRAY_SIZE(kManifestUrlSpecRequired),
                         error_string)) {
    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;
    error_stream << parent_key << " property '" << container_key <<
        "' has non-string value '" << url.toStyledString() <<
        "' for key '" << kUrlKey << "'.";
    *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;
}

// Validate a "pnacl-translate" dictionary, assuming it was resolved from
// container_key. E.g., "container_key" : { "pnacl_translate" : URLSpec }
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
  };
  if (!IsValidDictionary(pnacl_spec, container_key, parent_key,
                         kManifestPnaclSpecProperties,
                         NACL_ARRAY_SIZE(kManifestPnaclSpecProperties),
                         kManifestPnaclSpecProperties,
                         NACL_ARRAY_SIZE(kManifestPnaclSpecProperties),
                         error_string)) {
    return false;
  }
  Json::Value url_spec = pnacl_spec[kPnaclTranslateKey];
  if (!IsValidUrlSpec(url_spec, kPnaclTranslateKey,
                      container_key, sandbox_isa, error_string)) {
    return false;
  }
  return true;
}

// Validates that |dictionary| is a valid ISA dictionary.  An ISA dictionary
// is validated to have keys from within the set of recognized ISAs.  Unknown
// ISAs are allowed, but ignored and warnings are produced. It is also validated
// that it must have an entry to match the ISA specified in |sandbox_isa| or
// have a fallback 'portable' entry if there is no match. Returns true if
// |dictionary| is an ISA to URL map.  Sets |error_info| to something
// descriptive if it fails.
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;

  // An ISA to URL dictionary has to be an object.
  if (!dictionary.isObject()) {
    error_info->SetReport(ERROR_MANIFEST_SCHEMA_VALIDATE,
                          nacl::string("manifest: ") + parent_key +
                          " property is not an ISA to URL dictionary");
    return false;
  }
  // 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];
    Json::Value property_value = dictionary[property_name];
    nacl::string error_string;
    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 == 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);
    bool has_portable = dictionary.isMember(kPortableKey);

    if (!has_isa && !has_portable) {
      error_info->SetReport(
          ERROR_MANIFEST_PROGRAM_MISSING_ARCH,
          nacl::string("manifest: no version of ") + parent_key +
          " given for current arch and no portable version found.");
      return false;
    }
  }

  return true;
}

void GrabUrlAndPnaclOptions(const Json::Value& url_spec,
                            nacl::string* url,
                            PnaclOptions* pnacl_options) {
  *url = url_spec[kUrlKey].asString();
  if (url_spec.isMember(kOptLevelKey)) {
    int32_t opt_raw = url_spec[kOptLevelKey].asInt();
    // set_opt_level will normalize the values.
    pnacl_options->set_opt_level(opt_raw);
  }
}

bool GetURLFromISADictionary(const Json::Value& dictionary,
                             const nacl::string& parent_key,
                             const nacl::string& sandbox_isa,
                             nacl::string* url,
                             PnaclOptions* pnacl_options,
                             ErrorInfo* error_info) {
  if (url == NULL || pnacl_options == NULL || error_info == NULL)
    return false;

  // 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);
    return false;
  }

  *url = "";

  // The call to IsValidISADictionary() above guarantees that either
  // sandbox_isa or kPortableKey is present in the dictionary.
  bool has_portable = dictionary.isMember(kPortableKey);
  bool has_isa = dictionary.isMember(sandbox_isa);
  nacl::string chosen_isa;
  if ((sandbox_isa == kPortableKey) || (has_portable && !has_isa)) {
    chosen_isa = kPortableKey;
  } else {
    chosen_isa = sandbox_isa;
  }
  const Json::Value& isa_spec = dictionary[chosen_isa];
  // 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);
  }

  return true;
}

bool GetKeyUrl(const Json::Value& dictionary,
               const nacl::string& key,
               const nacl::string& sandbox_isa,
               const Manifest* manifest,
               nacl::string* full_url,
               PnaclOptions* pnacl_options,
               ErrorInfo* error_info) {
  CHECK(full_url != NULL && error_info != NULL);
  if (!dictionary.isMember(key)) {
    error_info->SetReport(ERROR_MANIFEST_RESOLVE_URL,
                          "file key not found in manifest");
    return false;
  }
  const Json::Value& isa_dict = dictionary[key];
  nacl::string relative_url;
  if (!GetURLFromISADictionary(isa_dict, key, sandbox_isa, &relative_url,
                               pnacl_options, error_info)) {
    return false;
  }
  return manifest->ResolveURL(relative_url, full_url, error_info);
}

}  // namespace

bool JsonManifest::Init(const nacl::string& manifest_json,
                        ErrorInfo* error_info) {
  if (error_info == NULL) {
    return false;
  }
  Json::Reader reader;
  if (!reader.parse(manifest_json, dictionary_)) {
    std::string json_error = reader.getFormatedErrorMessages();
    error_info->SetReport(ERROR_MANIFEST_PARSING,
                          "manifest JSON parsing failed: " + json_error);
    return false;
  }
  // Parse has ensured the string was valid JSON.  Check that it matches the
  // manifest schema.
  return MatchesSchema(error_info);
}

bool JsonManifest::MatchesSchema(ErrorInfo* error_info) {
  pp::Var exception;
  if (error_info == NULL) {
    return false;
  }
  if (!dictionary_.isObject()) {
    error_info->SetReport(
        ERROR_MANIFEST_SCHEMA_VALIDATE,
        "manifest: is not a json dictionary.");
    return false;
  }
  Json::Value::Members members = dictionary_.getMemberNames();
  for (size_t i = 0; i < members.size(); ++i) {
    // The top level dictionary entries valid in the manifest file.
    static const char* kManifestTopLevelProperties[] = { kProgramKey,
                                                         kInterpreterKey,
                                                         kFilesKey };
    nacl::string property_name = members[i];
    if (!FindMatchingProperty(property_name,
                              kManifestTopLevelProperties,
                              NACL_ARRAY_SIZE(kManifestTopLevelProperties))) {
      PLUGIN_PRINTF(("JsonManifest::MatchesSchema: WARNING: unknown top-level "
                     "section '%s' in manifest.\n", property_name.c_str()));
    }
  }

  // A manifest file must have a program section.
  if (!dictionary_.isMember(kProgramKey)) {
    error_info->SetReport(
        ERROR_MANIFEST_SCHEMA_VALIDATE,
        nacl::string("manifest: missing '") + kProgramKey + "' section.");
    return false;
  }

  // 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()) {
      error_info->SetReport(
          ERROR_MANIFEST_SCHEMA_VALIDATE,
          nacl::string("manifest: '") + kFilesKey + "' is not a dictionary.");
    }
    Json::Value::Members members = files.getMemberNames();
    for (size_t i = 0; i < members.size(); ++i) {
      nacl::string file_name = members[i];
      if (!IsValidISADictionary(files[file_name],
                                file_name,
                                sandbox_isa_,
                                false,
                                error_info)) {
        return false;
      }
    }
  }

  return true;
}

bool JsonManifest::ResolveURL(const nacl::string& relative_url,
                              nacl::string* full_url,
                              ErrorInfo* error_info) const {
  // The contents of the manifest are resolved relative to the manifest URL.
  CHECK(url_util_ != NULL);
  pp::Var resolved_url =
      url_util_->ResolveRelativeToURL(pp::Var(manifest_base_url_),
                                      relative_url);
  if (!resolved_url.is_string()) {
    error_info->SetReport(
        ERROR_MANIFEST_RESOLVE_URL,
        "could not resolve url '" + relative_url +
        "' relative to manifest base url '" + manifest_base_url_.c_str() +
        "'.");
    return false;
  }
  *full_url = resolved_url.AsString();
  return true;
}

bool JsonManifest::GetProgramURL(nacl::string* full_url,
                                 PnaclOptions* pnacl_options,
                                 ErrorInfo* error_info) const {
  if (full_url == NULL || pnacl_options == NULL || error_info == NULL)
    return false;

  Json::Value program = dictionary_[kProgramKey];

  nacl::string nexe_url;
  nacl::string error_string;

  if (!GetURLFromISADictionary(program,
                               kProgramKey,
                               sandbox_isa_,
                               &nexe_url,
                               pnacl_options,
                               error_info)) {
    return false;
  }

  return ResolveURL(nexe_url, full_url, error_info);
}

bool JsonManifest::GetFileKeys(std::set<nacl::string>* keys) const {
  if (!dictionary_.isMember(kFilesKey)) {
    // trivial success: no keys when there is no "files" section.
    return true;
  }
  const Json::Value& files = dictionary_[kFilesKey];
  CHECK(files.isObject());
  Json::Value::Members members = files.getMemberNames();
  for (size_t i = 0; i < members.size(); ++i) {
    keys->insert(members[i]);
  }
  return true;
}

bool JsonManifest::ResolveKey(const nacl::string& key,
                              nacl::string* full_url,
                              PnaclOptions* pnacl_options,
                              ErrorInfo* error_info) const {
  NaClLog(3, "JsonManifest::ResolveKey(%s)\n", key.c_str());
  // key must be one of kProgramKey or kFileKey '/' file-section-key

  if (full_url == NULL || pnacl_options == NULL || error_info == NULL)
    return false;

  if (key == kProgramKey) {
    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()) {
    error_info->SetReport(ERROR_MANIFEST_RESOLVE_URL,
                          nacl::string("ResolveKey: invalid key, no slash: ")
                          + key);
    return false;
  }

  // generalize to permit other sections?
  nacl::string prefix(key.begin(), p);
  if (prefix != kFilesKey) {
    error_info->SetReport(ERROR_MANIFEST_RESOLVE_URL,
                          nacl::string("ResolveKey: invalid key: not \"files\""
                                       " prefix: ") + key);
    return false;
  }

  nacl::string rest(p + 1, key.end());

  const Json::Value& files = dictionary_[kFilesKey];
  if (!files.isObject()) {
    error_info->SetReport(
        ERROR_MANIFEST_RESOLVE_URL,
        nacl::string("ResolveKey: no \"files\" dictionary"));
    return false;
  }
  if (!files.isMember(rest)) {
    error_info->SetReport(
        ERROR_MANIFEST_RESOLVE_URL,
        nacl::string("ResolveKey: no such \"files\" entry: ") + key);
    return false;
  }
  return GetKeyUrl(files, rest, sandbox_isa_, this, full_url, pnacl_options,
                   error_info);
}

}  // namespace plugin