| // Copyright 2015 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/safe_browsing/safe_browsing_api_handler_util.h" |
| |
| #include <stddef.h> |
| |
| #include <string> |
| |
| #include "base/json/json_reader.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/values.h" |
| #include "chrome/browser/safe_browsing/metadata.pb.h" |
| #include "chrome/browser/safe_browsing/safe_browsing_util.h" |
| |
| |
| namespace safe_browsing { |
| namespace { |
| |
| // JSON metatdata keys. These are are fixed in the Java-side API. |
| const char kJsonKeyMatches[] = "matches"; |
| const char kJsonKeyThreatType[] = "threat_type"; |
| |
| // Do not reorder or delete. Make sure changes are reflected in |
| // SB2RemoteCallThreatSubType. |
| enum UmaThreatSubType { |
| UMA_THREAT_SUB_TYPE_NOT_SET = 0, |
| UMA_THREAT_SUB_TYPE_LANDING = 1, |
| UMA_THREAT_SUB_TYPE_DISTRIBUTION = 2, |
| UMA_THREAT_SUB_TYPE_UNKNOWN = 3, |
| UMA_THREAT_SUB_TYPE_MAX_VALUE |
| }; |
| |
| void ReportUmaThreatSubType(SBThreatType threat_type, |
| UmaThreatSubType sub_type) { |
| if (threat_type == SB_THREAT_TYPE_URL_MALWARE) { |
| UMA_HISTOGRAM_ENUMERATION( |
| "SB2.RemoteCall.ThreatSubType.PotentiallyHarmfulApp", sub_type, |
| UMA_THREAT_SUB_TYPE_MAX_VALUE); |
| } else { |
| UMA_HISTOGRAM_ENUMERATION( |
| "SB2.RemoteCall.ThreatSubType.SocialEngineering", sub_type, |
| UMA_THREAT_SUB_TYPE_MAX_VALUE); |
| } |
| } |
| |
| // Parse the extra key/value pair(s) added by Safe Browsing backend. To make |
| // it binary compatible with the Pver3 metadata that UIManager expects to |
| // deserialize, we convert it to a MalwarePatternType. |
| // |
| // TODO(nparker): When chrome desktop is converted to use Pver4, convert this |
| // to the new proto instead. |
| const std::string ParseExtraMetadataToPB(const base::DictionaryValue* match, |
| SBThreatType threat_type) { |
| std::string pattern_key; |
| if (threat_type == SB_THREAT_TYPE_URL_MALWARE) { |
| pattern_key = "pha_pattern_type"; |
| } else { |
| DCHECK(threat_type == SB_THREAT_TYPE_URL_PHISHING); |
| pattern_key = "se_pattern_type"; |
| } |
| |
| std::string pattern_type; |
| if (!match->GetString(pattern_key, &pattern_type)) { |
| ReportUmaThreatSubType(threat_type, UMA_THREAT_SUB_TYPE_NOT_SET); |
| return std::string(); |
| } |
| |
| MalwarePatternType pb; |
| if (pattern_type == "LANDING") { |
| pb.set_pattern_type(MalwarePatternType::LANDING); |
| ReportUmaThreatSubType(threat_type, UMA_THREAT_SUB_TYPE_LANDING); |
| } else if (pattern_type == "DISTRIBUTION") { |
| pb.set_pattern_type(MalwarePatternType::DISTRIBUTION); |
| ReportUmaThreatSubType(threat_type, UMA_THREAT_SUB_TYPE_DISTRIBUTION); |
| } else { |
| ReportUmaThreatSubType(threat_type, UMA_THREAT_SUB_TYPE_UNKNOWN); |
| return std::string(); |
| } |
| |
| return pb.SerializeAsString(); |
| } |
| |
| int GetThreatSeverity(int java_threat_num) { |
| // Assign higher numbers to more severe threats. |
| switch (java_threat_num) { |
| case JAVA_THREAT_TYPE_POTENTIALLY_HARMFUL_APPLICATION: |
| return 2; |
| case JAVA_THREAT_TYPE_SOCIAL_ENGINEERING: |
| return 1; |
| default: |
| // Unknown threat type |
| return -1; |
| } |
| } |
| |
| SBThreatType JavaToSBThreatType(int java_threat_num) { |
| switch (java_threat_num) { |
| case JAVA_THREAT_TYPE_POTENTIALLY_HARMFUL_APPLICATION: |
| return SB_THREAT_TYPE_URL_MALWARE; |
| case JAVA_THREAT_TYPE_SOCIAL_ENGINEERING: |
| return SB_THREAT_TYPE_URL_PHISHING; |
| default: |
| // Unknown threat type |
| return SB_THREAT_TYPE_SAFE; |
| } |
| } |
| |
| } // namespace |
| |
| |
| // Valid examples: |
| // {"matches":[{"threat_type":"5"}]} |
| // or |
| // {"matches":[{"threat_type":"4"}, |
| // {"threat_type":"5", "se_pattern_type":"LANDING"}]} |
| UmaRemoteCallResult ParseJsonToThreatAndPB(const std::string& metadata_str, |
| SBThreatType* worst_threat, |
| std::string* metadata_pb_str) { |
| *worst_threat = SB_THREAT_TYPE_SAFE; // Default to safe. |
| *metadata_pb_str = std::string(); |
| |
| if (metadata_str.empty()) |
| return UMA_STATUS_JSON_EMPTY; |
| |
| // Pick out the "matches" list. |
| scoped_ptr<base::Value> value = base::JSONReader::Read(metadata_str); |
| const base::ListValue* matches; |
| if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY) || |
| !(static_cast<base::DictionaryValue*>(value.get())) |
| ->GetList(kJsonKeyMatches, &matches)) { |
| return UMA_STATUS_JSON_FAILED_TO_PARSE; |
| } |
| |
| // Go through each matched threat type and pick the most severe. |
| int worst_threat_num = -1; |
| const base::DictionaryValue* worst_match = nullptr; |
| for (size_t i = 0; i < matches->GetSize(); i++) { |
| // Get the threat number |
| const base::DictionaryValue* match; |
| std::string threat_num_str; |
| int java_threat_num = -1; |
| if (!matches->GetDictionary(i, &match) || |
| !match->GetString(kJsonKeyThreatType, &threat_num_str) || |
| !base::StringToInt(threat_num_str, &java_threat_num)) { |
| continue; // Skip malformed list entries |
| } |
| |
| if (GetThreatSeverity(java_threat_num) > |
| GetThreatSeverity(worst_threat_num)) { |
| worst_threat_num = java_threat_num; |
| worst_match = match; |
| } |
| } |
| |
| *worst_threat = JavaToSBThreatType(worst_threat_num); |
| if (*worst_threat == SB_THREAT_TYPE_SAFE || !worst_match) |
| return UMA_STATUS_JSON_UNKNOWN_THREAT; |
| *metadata_pb_str = ParseExtraMetadataToPB(worst_match, *worst_threat); |
| |
| return UMA_STATUS_UNSAFE; // success |
| } |
| |
| } // namespace safe_browsing |