// Copyright 2013 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 "components/nacl/loader/nacl_validation_query.h" #include "base/logging.h" #include "components/nacl/loader/nacl_validation_db.h" #include "crypto/nss_util.h" #include "native_client/src/include/portability.h" #include "native_client/src/trusted/validator/nacl_file_info.h" #include "native_client/src/trusted/validator/validation_cache.h" NaClValidationQueryContext::NaClValidationQueryContext( NaClValidationDB* db, const std::string& profile_key, const std::string& nacl_version) : db_(db), profile_key_(profile_key), nacl_version_(nacl_version) { // Sanity checks. CHECK(profile_key.length() >= 8); CHECK(nacl_version.length() >= 4); } NaClValidationQuery* NaClValidationQueryContext::CreateQuery() { NaClValidationQuery* query = new NaClValidationQuery(db_, profile_key_); // Changing the version effectively invalidates existing hashes. query->AddData(nacl_version_); return query; } bool NaClValidationQueryContext::ResolveFileToken( struct NaClFileToken* file_token, int32* fd, std::string* path) { return db_->ResolveFileToken(file_token, fd, path); } NaClValidationQuery::NaClValidationQuery(NaClValidationDB* db, const std::string& profile_key) : state_(READY), hasher_(crypto::HMAC::SHA256), db_(db), buffer_length_(0) { // Without this line on Linux, HMAC::Init will instantiate a singleton that // in turn attempts to open a file. Disabling this behavior avoids a ~70 ms // stall the first time HMAC is used. // This function is also called in nacl_helper_linux.cc, but nacl_helper may // not be used in all cases. // TODO(ncbray) remove when nacl_helper becomes the only code path. // http://code.google.com/p/chromium/issues/detail?id=118263 #if defined(USE_NSS) crypto::ForceNSSNoDBInit(); #endif CHECK(hasher_.Init(profile_key)); } void NaClValidationQuery::AddData(const char* data, size_t length) { CHECK(state_ == READY); CHECK(buffer_length_ <= sizeof(buffer_)); // Chrome's HMAC class doesn't support incremental signing. Work around // this by using a (small) temporary buffer to accumulate data. // Check if there is space in the buffer. if (buffer_length_ + kDigestLength > sizeof(buffer_)) { // Hash the buffer to make space. CompressBuffer(); } // Hash the input data into the buffer. Assumes that sizeof(buffer_) >= // kDigestLength * 2 (the buffer can store at least two digests.) CHECK(hasher_.Sign(base::StringPiece(data, length), reinterpret_cast<unsigned char*>(buffer_ + buffer_length_), kDigestLength)); buffer_length_ += kDigestLength; } void NaClValidationQuery::AddData(const unsigned char* data, size_t length) { AddData(reinterpret_cast<const char*>(data), length); } void NaClValidationQuery::AddData(const base::StringPiece& data) { AddData(data.data(), data.length()); } int NaClValidationQuery::QueryKnownToValidate() { CHECK(state_ == READY); // It is suspicious if we have less than a digest's worth of data. CHECK(buffer_length_ >= kDigestLength); CHECK(buffer_length_ <= sizeof(buffer_)); state_ = GET_CALLED; // Ensure the buffer contains only one digest worth of data. CompressBuffer(); return db_->QueryKnownToValidate(std::string(buffer_, buffer_length_)); } void NaClValidationQuery::SetKnownToValidate() { CHECK(state_ == GET_CALLED); CHECK(buffer_length_ == kDigestLength); state_ = SET_CALLED; db_->SetKnownToValidate(std::string(buffer_, buffer_length_)); } // Reduce the size of the data in the buffer by hashing it and writing it back // to the buffer. void NaClValidationQuery::CompressBuffer() { // Calculate the digest into a temp buffer. It is likely safe to calculate it // directly back into the buffer, but this is an "accidental" semantic we're // avoiding depending on. unsigned char temp[kDigestLength]; CHECK(hasher_.Sign(base::StringPiece(buffer_, buffer_length_), temp, kDigestLength)); memcpy(buffer_, temp, kDigestLength); buffer_length_ = kDigestLength; } // OO wrappers static void* CreateQuery(void* handle) { return static_cast<NaClValidationQueryContext*>(handle)->CreateQuery(); } static void AddData(void* query, const uint8* data, size_t length) { static_cast<NaClValidationQuery*>(query)->AddData(data, length); } static int QueryKnownToValidate(void* query) { return static_cast<NaClValidationQuery*>(query)->QueryKnownToValidate(); } static void SetKnownToValidate(void* query) { static_cast<NaClValidationQuery*>(query)->SetKnownToValidate(); } static void DestroyQuery(void* query) { delete static_cast<NaClValidationQuery*>(query); } static int ResolveFileToken(void* handle, struct NaClFileToken* file_token, int32* fd, char** file_path, uint32* file_path_length) { std::string path; *file_path = NULL; *file_path_length = 0; bool ok = static_cast<NaClValidationQueryContext*>(handle)-> ResolveFileToken(file_token, fd, &path); if (ok) { *file_path = static_cast<char*>(malloc(path.length() + 1)); CHECK(*file_path); memcpy(*file_path, path.data(), path.length()); (*file_path)[path.length()] = 0; *file_path_length = static_cast<uint32>(path.length()); } return ok; } struct NaClValidationCache* CreateValidationCache( NaClValidationDB* db, const std::string& profile_key, const std::string& nacl_version) { NaClValidationCache* cache = static_cast<NaClValidationCache*>(malloc(sizeof(NaClValidationCache))); // Make sure any fields introduced in a cross-repo change are zeroed. memset(cache, 0, sizeof(*cache)); cache->handle = new NaClValidationQueryContext(db, profile_key, nacl_version); cache->CreateQuery = CreateQuery; cache->AddData = AddData; cache->QueryKnownToValidate = QueryKnownToValidate; cache->SetKnownToValidate = SetKnownToValidate; cache->DestroyQuery = DestroyQuery; cache->ResolveFileToken = ResolveFileToken; return cache; }