// 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 "chrome/browser/profile_resetter/jtl_interpreter.h" #include #include "base/memory/scoped_vector.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "chrome/browser/profile_resetter/jtl_foundation.h" #include "crypto/hmac.h" #include "crypto/sha2.h" #include "net/base/registry_controlled_domains/registry_controlled_domain.h" #include "url/gurl.h" namespace { class ExecutionContext; // An operation in an interpreted program. class Operation { public: virtual ~Operation() {} // Executes the operation on the specified context and instructs the context // to continue execution with the next instruction if appropriate. // Returns true if we should continue with any potential backtracking that // needs to be done. virtual bool Execute(ExecutionContext* context) = 0; }; // An execution context of operations. class ExecutionContext { public: // |input| is the root of a dictionary that stores the information the // sentence is evaluated on. ExecutionContext(const jtl_foundation::Hasher* hasher, const std::vector& sentence, const base::DictionaryValue* input, base::DictionaryValue* working_memory) : hasher_(hasher), sentence_(sentence), next_instruction_index_(0u), working_memory_(working_memory), error_(false) { stack_.push_back(input); } ~ExecutionContext() {} // Returns true in case of success. bool ContinueExecution() { if (error_ || stack_.empty()) { error_ = true; return false; } if (next_instruction_index_ >= sentence_.size()) return true; Operation* op = sentence_[next_instruction_index_]; next_instruction_index_++; bool continue_traversal = op->Execute(this); next_instruction_index_--; return continue_traversal; } std::string GetHash(const std::string& input) { return hasher_->GetHash(input); } // Calculates the |hash| of a string, integer or double |value|, and returns // true. Returns false otherwise. bool GetValueHash(const base::Value& value, std::string* hash) { DCHECK(hash); std::string value_as_string; int tmp_int = 0; double tmp_double = 0.0; if (value.GetAsInteger(&tmp_int)) value_as_string = base::IntToString(tmp_int); else if (value.GetAsDouble(&tmp_double)) value_as_string = base::DoubleToString(tmp_double); else if (!value.GetAsString(&value_as_string)) return false; *hash = GetHash(value_as_string); return true; } const base::Value* current_node() const { return stack_.back(); } std::vector* stack() { return &stack_; } base::DictionaryValue* working_memory() { return working_memory_; } bool error() const { return error_; } private: // A hasher used to hash node names in a dictionary. const jtl_foundation::Hasher* hasher_; // The sentence to be executed. const std::vector sentence_; // Position in |sentence_|. size_t next_instruction_index_; // A stack of Values, indicating a navigation path from the root node of // |input| (see constructor) to the current node on which the // sentence_[next_instruction_index_] is evaluated. std::vector stack_; // Memory into which values can be stored by the program. base::DictionaryValue* working_memory_; // Whether a runtime error occurred. bool error_; DISALLOW_COPY_AND_ASSIGN(ExecutionContext); }; class NavigateOperation : public Operation { public: explicit NavigateOperation(const std::string& hashed_key) : hashed_key_(hashed_key) {} ~NavigateOperation() override {} bool Execute(ExecutionContext* context) override { const base::DictionaryValue* dict = NULL; if (!context->current_node()->GetAsDictionary(&dict)) { // Just ignore this node gracefully as this navigation is a dead end. // If this NavigateOperation occurred after a NavigateAny operation, those // may still be fulfillable, so we allow continuing the execution of the // sentence on other nodes. return true; } for (base::DictionaryValue::Iterator i(*dict); !i.IsAtEnd(); i.Advance()) { if (context->GetHash(i.key()) != hashed_key_) continue; context->stack()->push_back(&i.value()); bool continue_traversal = context->ContinueExecution(); context->stack()->pop_back(); if (!continue_traversal) return false; } return true; } private: std::string hashed_key_; DISALLOW_COPY_AND_ASSIGN(NavigateOperation); }; class NavigateAnyOperation : public Operation { public: NavigateAnyOperation() {} ~NavigateAnyOperation() override {} bool Execute(ExecutionContext* context) override { const base::DictionaryValue* dict = NULL; const base::ListValue* list = NULL; if (context->current_node()->GetAsDictionary(&dict)) { for (base::DictionaryValue::Iterator i(*dict); !i.IsAtEnd(); i.Advance()) { context->stack()->push_back(&i.value()); bool continue_traversal = context->ContinueExecution(); context->stack()->pop_back(); if (!continue_traversal) return false; } } else if (context->current_node()->GetAsList(&list)) { for (base::ListValue::const_iterator i = list->begin(); i != list->end(); ++i) { context->stack()->push_back(*i); bool continue_traversal = context->ContinueExecution(); context->stack()->pop_back(); if (!continue_traversal) return false; } } else { // Do nothing, just ignore this node. } return true; } private: DISALLOW_COPY_AND_ASSIGN(NavigateAnyOperation); }; class NavigateBackOperation : public Operation { public: NavigateBackOperation() {} ~NavigateBackOperation() override {} bool Execute(ExecutionContext* context) override { const base::Value* current_node = context->current_node(); context->stack()->pop_back(); bool continue_traversal = context->ContinueExecution(); context->stack()->push_back(current_node); return continue_traversal; } private: DISALLOW_COPY_AND_ASSIGN(NavigateBackOperation); }; class StoreValue : public Operation { public: StoreValue(const std::string& hashed_name, scoped_ptr value) : hashed_name_(hashed_name), value_(value.Pass()) { DCHECK(base::IsStringUTF8(hashed_name)); DCHECK(value_); } ~StoreValue() override {} bool Execute(ExecutionContext* context) override { context->working_memory()->Set(hashed_name_, value_->DeepCopy()); return context->ContinueExecution(); } private: std::string hashed_name_; scoped_ptr value_; DISALLOW_COPY_AND_ASSIGN(StoreValue); }; class CompareStoredValue : public Operation { public: CompareStoredValue(const std::string& hashed_name, scoped_ptr value, scoped_ptr default_value) : hashed_name_(hashed_name), value_(value.Pass()), default_value_(default_value.Pass()) { DCHECK(base::IsStringUTF8(hashed_name)); DCHECK(value_); DCHECK(default_value_); } ~CompareStoredValue() override {} bool Execute(ExecutionContext* context) override { const base::Value* actual_value = NULL; if (!context->working_memory()->Get(hashed_name_, &actual_value)) actual_value = default_value_.get(); if (!value_->Equals(actual_value)) return true; return context->ContinueExecution(); } private: std::string hashed_name_; scoped_ptr value_; scoped_ptr default_value_; DISALLOW_COPY_AND_ASSIGN(CompareStoredValue); }; template class StoreNodeValue : public Operation { public: explicit StoreNodeValue(const std::string& hashed_name) : hashed_name_(hashed_name) { DCHECK(base::IsStringUTF8(hashed_name)); } ~StoreNodeValue() override {} bool Execute(ExecutionContext* context) override { scoped_ptr value; if (ExpectedTypeIsBooleanNotHashable) { if (!context->current_node()->IsType(base::Value::TYPE_BOOLEAN)) return true; value.reset(context->current_node()->DeepCopy()); } else { std::string hash; if (!context->GetValueHash(*context->current_node(), &hash)) return true; value.reset(new base::StringValue(hash)); } context->working_memory()->Set(hashed_name_, value.release()); return context->ContinueExecution(); } private: std::string hashed_name_; DISALLOW_COPY_AND_ASSIGN(StoreNodeValue); }; // Stores the hash of the registerable domain name -- as in, the portion of the // domain that is registerable, as opposed to controlled by a registrar; without // subdomains -- of the URL represented by the current node into working memory. class StoreNodeRegisterableDomain : public Operation { public: explicit StoreNodeRegisterableDomain(const std::string& hashed_name) : hashed_name_(hashed_name) { DCHECK(base::IsStringUTF8(hashed_name)); } ~StoreNodeRegisterableDomain() override {} bool Execute(ExecutionContext* context) override { std::string possibly_invalid_url; std::string domain; if (!context->current_node()->GetAsString(&possibly_invalid_url) || !GetRegisterableDomain(possibly_invalid_url, &domain)) return true; context->working_memory()->Set( hashed_name_, new base::StringValue(context->GetHash(domain))); return context->ContinueExecution(); } private: // If |possibly_invalid_url| is a valid URL having a registerable domain name // part, outputs that in |registerable_domain| and returns true. Otherwise, // returns false. static bool GetRegisterableDomain(const std::string& possibly_invalid_url, std::string* registerable_domain) { namespace domains = net::registry_controlled_domains; DCHECK(registerable_domain); GURL url(possibly_invalid_url); if (!url.is_valid()) return false; std::string registry_plus_one = domains::GetDomainAndRegistry( url.host(), domains::INCLUDE_PRIVATE_REGISTRIES); size_t registry_length = domains::GetRegistryLength( url.host(), domains::INCLUDE_UNKNOWN_REGISTRIES, domains::INCLUDE_PRIVATE_REGISTRIES); // Fail unless (1.) the URL has a host part; and (2.) that host part is a // well-formed domain name consisting of at least one subcomponent; followed // by either a recognized registry identifier, or exactly one subcomponent, // which is then assumed to be the unknown registry identifier. if (registry_length == std::string::npos || registry_length == 0) return false; DCHECK_LT(registry_length, registry_plus_one.size()); // Subtract one to cut off the dot separating the SLD and the registry. registerable_domain->assign( registry_plus_one, 0, registry_plus_one.size() - registry_length - 1); return true; } std::string hashed_name_; DISALLOW_COPY_AND_ASSIGN(StoreNodeRegisterableDomain); }; class CompareNodeBool : public Operation { public: explicit CompareNodeBool(bool value) : value_(value) {} ~CompareNodeBool() override {} bool Execute(ExecutionContext* context) override { bool actual_value = false; if (!context->current_node()->GetAsBoolean(&actual_value)) return true; if (actual_value != value_) return true; return context->ContinueExecution(); } private: bool value_; DISALLOW_COPY_AND_ASSIGN(CompareNodeBool); }; class CompareNodeHash : public Operation { public: explicit CompareNodeHash(const std::string& hashed_value) : hashed_value_(hashed_value) {} ~CompareNodeHash() override {} bool Execute(ExecutionContext* context) override { std::string actual_hash; if (!context->GetValueHash(*context->current_node(), &actual_hash) || actual_hash != hashed_value_) return true; return context->ContinueExecution(); } private: std::string hashed_value_; DISALLOW_COPY_AND_ASSIGN(CompareNodeHash); }; class CompareNodeHashNot : public Operation { public: explicit CompareNodeHashNot(const std::string& hashed_value) : hashed_value_(hashed_value) {} ~CompareNodeHashNot() override {} bool Execute(ExecutionContext* context) override { std::string actual_hash; if (context->GetValueHash(*context->current_node(), &actual_hash) && actual_hash == hashed_value_) return true; return context->ContinueExecution(); } private: std::string hashed_value_; DISALLOW_COPY_AND_ASSIGN(CompareNodeHashNot); }; template class CompareNodeToStored : public Operation { public: explicit CompareNodeToStored(const std::string& hashed_name) : hashed_name_(hashed_name) {} ~CompareNodeToStored() override {} bool Execute(ExecutionContext* context) override { const base::Value* stored_value = NULL; if (!context->working_memory()->Get(hashed_name_, &stored_value)) return true; if (ExpectedTypeIsBooleanNotHashable) { if (!context->current_node()->IsType(base::Value::TYPE_BOOLEAN) || !context->current_node()->Equals(stored_value)) return true; } else { std::string actual_hash; std::string stored_hash; if (!context->GetValueHash(*context->current_node(), &actual_hash) || !stored_value->GetAsString(&stored_hash) || actual_hash != stored_hash) return true; } return context->ContinueExecution(); } private: std::string hashed_name_; DISALLOW_COPY_AND_ASSIGN(CompareNodeToStored); }; class CompareNodeSubstring : public Operation { public: explicit CompareNodeSubstring(const std::string& hashed_pattern, size_t pattern_length, uint32 pattern_sum) : hashed_pattern_(hashed_pattern), pattern_length_(pattern_length), pattern_sum_(pattern_sum) { DCHECK(pattern_length_); } ~CompareNodeSubstring() override {} bool Execute(ExecutionContext* context) override { std::string value_as_string; if (!context->current_node()->GetAsString(&value_as_string) || !pattern_length_ || value_as_string.size() < pattern_length_) return true; // Go over the string with a sliding window. Meanwhile, maintain the sum in // an incremental fashion, and only calculate the SHA-256 hash when the sum // checks out so as to improve performance. std::string::const_iterator window_begin = value_as_string.begin(); std::string::const_iterator window_end = window_begin + pattern_length_ - 1; uint32 window_sum = std::accumulate(window_begin, window_end, static_cast(0u)); while (window_end != value_as_string.end()) { window_sum += *window_end++; if (window_sum == pattern_sum_ && context->GetHash(std::string( window_begin, window_end)) == hashed_pattern_) return context->ContinueExecution(); window_sum -= *window_begin++; } return true; } private: std::string hashed_pattern_; size_t pattern_length_; uint32 pattern_sum_; DISALLOW_COPY_AND_ASSIGN(CompareNodeSubstring); }; class StopExecutingSentenceOperation : public Operation { public: StopExecutingSentenceOperation() {} ~StopExecutingSentenceOperation() override {} bool Execute(ExecutionContext* context) override { return false; } private: DISALLOW_COPY_AND_ASSIGN(StopExecutingSentenceOperation); }; class Parser { public: explicit Parser(const std::string& program) : program_(program), next_instruction_index_(0u) {} ~Parser() {} bool ParseNextSentence(ScopedVector* output) { ScopedVector operators; bool sentence_ended = false; while (next_instruction_index_ < program_.size() && !sentence_ended) { uint8 op_code = 0; if (!ReadOpCode(&op_code)) return false; switch (static_cast(op_code)) { case jtl_foundation::NAVIGATE: { std::string hashed_key; if (!ReadHash(&hashed_key)) return false; operators.push_back(new NavigateOperation(hashed_key)); break; } case jtl_foundation::NAVIGATE_ANY: operators.push_back(new NavigateAnyOperation); break; case jtl_foundation::NAVIGATE_BACK: operators.push_back(new NavigateBackOperation); break; case jtl_foundation::STORE_BOOL: { std::string hashed_name; if (!ReadHash(&hashed_name) || !base::IsStringUTF8(hashed_name)) return false; bool value = false; if (!ReadBool(&value)) return false; operators.push_back(new StoreValue( hashed_name, scoped_ptr(new base::FundamentalValue(value)))); break; } case jtl_foundation::COMPARE_STORED_BOOL: { std::string hashed_name; if (!ReadHash(&hashed_name) || !base::IsStringUTF8(hashed_name)) return false; bool value = false; if (!ReadBool(&value)) return false; bool default_value = false; if (!ReadBool(&default_value)) return false; operators.push_back(new CompareStoredValue( hashed_name, scoped_ptr(new base::FundamentalValue(value)), scoped_ptr( new base::FundamentalValue(default_value)))); break; } case jtl_foundation::STORE_HASH: { std::string hashed_name; if (!ReadHash(&hashed_name) || !base::IsStringUTF8(hashed_name)) return false; std::string hashed_value; if (!ReadHash(&hashed_value)) return false; operators.push_back(new StoreValue( hashed_name, scoped_ptr(new base::StringValue(hashed_value)))); break; } case jtl_foundation::COMPARE_STORED_HASH: { std::string hashed_name; if (!ReadHash(&hashed_name) || !base::IsStringUTF8(hashed_name)) return false; std::string hashed_value; if (!ReadHash(&hashed_value)) return false; std::string hashed_default_value; if (!ReadHash(&hashed_default_value)) return false; operators.push_back(new CompareStoredValue( hashed_name, scoped_ptr(new base::StringValue(hashed_value)), scoped_ptr( new base::StringValue(hashed_default_value)))); break; } case jtl_foundation::STORE_NODE_BOOL: { std::string hashed_name; if (!ReadHash(&hashed_name) || !base::IsStringUTF8(hashed_name)) return false; operators.push_back(new StoreNodeValue(hashed_name)); break; } case jtl_foundation::STORE_NODE_HASH: { std::string hashed_name; if (!ReadHash(&hashed_name) || !base::IsStringUTF8(hashed_name)) return false; operators.push_back(new StoreNodeValue(hashed_name)); break; } case jtl_foundation::STORE_NODE_REGISTERABLE_DOMAIN_HASH: { std::string hashed_name; if (!ReadHash(&hashed_name) || !base::IsStringUTF8(hashed_name)) return false; operators.push_back(new StoreNodeRegisterableDomain(hashed_name)); break; } case jtl_foundation::COMPARE_NODE_BOOL: { bool value = false; if (!ReadBool(&value)) return false; operators.push_back(new CompareNodeBool(value)); break; } case jtl_foundation::COMPARE_NODE_HASH: { std::string hashed_value; if (!ReadHash(&hashed_value)) return false; operators.push_back(new CompareNodeHash(hashed_value)); break; } case jtl_foundation::COMPARE_NODE_HASH_NOT: { std::string hashed_value; if (!ReadHash(&hashed_value)) return false; operators.push_back(new CompareNodeHashNot(hashed_value)); break; } case jtl_foundation::COMPARE_NODE_TO_STORED_BOOL: { std::string hashed_name; if (!ReadHash(&hashed_name) || !base::IsStringUTF8(hashed_name)) return false; operators.push_back(new CompareNodeToStored(hashed_name)); break; } case jtl_foundation::COMPARE_NODE_TO_STORED_HASH: { std::string hashed_name; if (!ReadHash(&hashed_name) || !base::IsStringUTF8(hashed_name)) return false; operators.push_back(new CompareNodeToStored(hashed_name)); break; } case jtl_foundation::COMPARE_NODE_SUBSTRING: { std::string hashed_pattern; uint32 pattern_length = 0, pattern_sum = 0; if (!ReadHash(&hashed_pattern)) return false; if (!ReadUint32(&pattern_length) || pattern_length == 0) return false; if (!ReadUint32(&pattern_sum)) return false; operators.push_back(new CompareNodeSubstring( hashed_pattern, pattern_length, pattern_sum)); break; } case jtl_foundation::STOP_EXECUTING_SENTENCE: operators.push_back(new StopExecutingSentenceOperation); break; case jtl_foundation::END_OF_SENTENCE: sentence_ended = true; break; default: return false; } } output->swap(operators); return true; } bool HasNextSentence() const { return next_instruction_index_ < program_.size(); } private: // Reads an uint8 and returns whether this operation was successful. bool ReadUint8(uint8* out) { DCHECK(out); if (next_instruction_index_ + 1u > program_.size()) return false; *out = static_cast(program_[next_instruction_index_]); ++next_instruction_index_; return true; } // Reads an uint32 and returns whether this operation was successful. bool ReadUint32(uint32* out) { DCHECK(out); if (next_instruction_index_ + 4u > program_.size()) return false; *out = 0u; for (int i = 0; i < 4; ++i) { *out >>= 8; *out |= static_cast(program_[next_instruction_index_]) << 24; ++next_instruction_index_; } return true; } // Reads an operator code and returns whether this operation was successful. bool ReadOpCode(uint8* out) { return ReadUint8(out); } bool ReadHash(std::string* out) { DCHECK(out); if (next_instruction_index_ + jtl_foundation::kHashSizeInBytes > program_.size()) return false; *out = program_.substr(next_instruction_index_, jtl_foundation::kHashSizeInBytes); next_instruction_index_ += jtl_foundation::kHashSizeInBytes; DCHECK(jtl_foundation::Hasher::IsHash(*out)); return true; } bool ReadBool(bool* out) { DCHECK(out); uint8 value = 0; if (!ReadUint8(&value)) return false; if (value == 0) *out = false; else if (value == 1) *out = true; else return false; return true; } std::string program_; size_t next_instruction_index_; DISALLOW_COPY_AND_ASSIGN(Parser); }; } // namespace JtlInterpreter::JtlInterpreter( const std::string& hasher_seed, const std::string& program, const base::DictionaryValue* input) : hasher_seed_(hasher_seed), program_(program), input_(input), working_memory_(new base::DictionaryValue), result_(OK) { DCHECK(input->IsType(base::Value::TYPE_DICTIONARY)); } JtlInterpreter::~JtlInterpreter() {} void JtlInterpreter::Execute() { jtl_foundation::Hasher hasher(hasher_seed_); Parser parser(program_); while (parser.HasNextSentence()) { ScopedVector sentence; if (!parser.ParseNextSentence(&sentence)) { result_ = PARSE_ERROR; return; } ExecutionContext context( &hasher, sentence.get(), input_, working_memory_.get()); context.ContinueExecution(); if (context.error()) { result_ = RUNTIME_ERROR; return; } } } bool JtlInterpreter::GetOutputBoolean(const std::string& unhashed_key, bool* output) const { std::string hashed_key = jtl_foundation::Hasher(hasher_seed_).GetHash(unhashed_key); return working_memory_->GetBoolean(hashed_key, output); } bool JtlInterpreter::GetOutputString(const std::string& unhashed_key, std::string* output) const { std::string hashed_key = jtl_foundation::Hasher(hasher_seed_).GetHash(unhashed_key); return working_memory_->GetString(hashed_key, output); } int JtlInterpreter::CalculateProgramChecksum() const { uint8 digest[3] = {}; crypto::SHA256HashString(program_, digest, arraysize(digest)); return static_cast(digest[0]) << 16 | static_cast(digest[1]) << 8 | static_cast(digest[2]); }