
Enabled by default since M114. Bug: 356235614 Change-Id: I2c52efe5a5ee72f7b63e3fde8477aae953194116 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5999534 Commit-Queue: Morten Stenshorne <mstensho@chromium.org> Reviewed-by: Lei Zhang <thestig@chromium.org> Reviewed-by: Anunoy Ghosh <anunoy@chromium.org> Cr-Commit-Position: refs/heads/main@{#1379899}
338 lines
14 KiB
C++
338 lines
14 KiB
C++
// Copyright 2024 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "chrome/browser/extensions/extension_safety_check_utils.h"
|
|
|
|
#include "chrome/browser/extensions/api/developer_private/developer_private_api.h"
|
|
#include "chrome/browser/extensions/cws_info_service.h"
|
|
#include "chrome/browser/extensions/extension_management.h"
|
|
#include "chrome/common/chrome_features.h"
|
|
#include "chrome/common/pref_names.h"
|
|
#include "chrome/grit/branded_strings.h"
|
|
#include "chrome/grit/generated_resources.h"
|
|
#include "components/prefs/pref_service.h"
|
|
#include "extensions/browser/blocklist_extension_prefs.h"
|
|
#include "extensions/browser/blocklist_state.h"
|
|
#include "extensions/browser/extension_prefs.h"
|
|
#include "ui/base/l10n/l10n_util.h"
|
|
|
|
namespace extensions {
|
|
|
|
namespace developer = api::developer_private;
|
|
|
|
namespace {
|
|
|
|
// Update the old kPrefAcknowledgeSafetyCheckWarning pref to the new
|
|
// kPrefAcknowledgeSafetyCheckWarningReason pref. If only the boolean
|
|
// acknowledged pref is present, it's replaced with the new acknowledge
|
|
// reason pref set to the current top warning reason. If both the
|
|
// boolean and reason acknowledged pref are present, the bool pref is
|
|
// removed.
|
|
void MigrateSafetyCheckAcknowledgePref(
|
|
const Extension& extension,
|
|
developer::SafetyCheckWarningReason acknowledged_reason,
|
|
developer::SafetyCheckWarningReason top_warning_reason,
|
|
ExtensionPrefs* extension_prefs) {
|
|
bool extension_kept = false;
|
|
extension_prefs->ReadPrefAsBoolean(
|
|
extension.id(), extensions::kPrefAcknowledgeSafetyCheckWarning,
|
|
&extension_kept);
|
|
if (!extension_kept) {
|
|
return;
|
|
}
|
|
if (acknowledged_reason == developer::SafetyCheckWarningReason::kNone) {
|
|
extension_prefs->SetIntegerPref(
|
|
extension.id(), extensions::kPrefAcknowledgeSafetyCheckWarningReason,
|
|
static_cast<int>(top_warning_reason));
|
|
}
|
|
// Remove the old boolean pref.
|
|
extension_prefs->UpdateExtensionPref(
|
|
extension.id(), extensions::kPrefAcknowledgeSafetyCheckWarning.name,
|
|
std::nullopt);
|
|
}
|
|
|
|
// Returns true if the Safety Check should display a malware warning.
|
|
bool SafetyCheckShouldShowMalware(
|
|
BitMapBlocklistState blocklist_state,
|
|
const std::optional<CWSInfoService::CWSInfo>& cws_info) {
|
|
bool valid_cws_info = cws_info.has_value() && cws_info->is_present;
|
|
bool has_safe_browsing_malware_rating =
|
|
blocklist_state == BitMapBlocklistState::BLOCKLISTED_MALWARE;
|
|
bool has_cws_malware_rating =
|
|
valid_cws_info &&
|
|
cws_info->violation_type == CWSInfoService::CWSViolationType::kMalware;
|
|
bool is_malware = has_safe_browsing_malware_rating || has_cws_malware_rating;
|
|
return is_malware;
|
|
}
|
|
|
|
// Returns true if the Safety Check should display a policy violation warning.
|
|
bool SafetyCheckShouldShowPolicyViolation(
|
|
BitMapBlocklistState blocklist_state,
|
|
const std::optional<CWSInfoService::CWSInfo>& cws_info) {
|
|
bool valid_cws_info = cws_info.has_value() && cws_info->is_present;
|
|
bool has_safe_browsing_policy_rating =
|
|
blocklist_state == BitMapBlocklistState::BLOCKLISTED_CWS_POLICY_VIOLATION;
|
|
bool has_cws_policy_rating =
|
|
valid_cws_info &&
|
|
cws_info->violation_type == CWSInfoService::CWSViolationType::kPolicy;
|
|
bool is_policy_violation =
|
|
has_safe_browsing_policy_rating || has_cws_policy_rating;
|
|
return is_policy_violation;
|
|
}
|
|
|
|
// Returns true if the Safety Check should display an unwanted software warning.
|
|
bool SafetyCheckShouldShowPotentiallyUnwanted(
|
|
BitMapBlocklistState blocklist_state) {
|
|
bool is_potentially_unwanted =
|
|
blocklist_state == BitMapBlocklistState::BLOCKLISTED_POTENTIALLY_UNWANTED;
|
|
bool potentially_unwanted_enabled =
|
|
base::FeatureList::IsEnabled(features::kSafetyHubExtensionsUwSTrigger);
|
|
return potentially_unwanted_enabled && is_potentially_unwanted;
|
|
}
|
|
|
|
// Returns true if the Safety Check should display a no privacy practice
|
|
// warning.
|
|
bool SafetyCheckShouldShowNoPrivacyPractice(
|
|
const std::optional<CWSInfoService::CWSInfo>& cws_info) {
|
|
bool valid_cws_info = cws_info.has_value() && cws_info->is_present;
|
|
bool no_privacy_practice_enabled = base::FeatureList::IsEnabled(
|
|
features::kSafetyHubExtensionsNoPrivacyPracticesTrigger);
|
|
bool has_no_privacy_practice_info =
|
|
valid_cws_info && cws_info->no_privacy_practice;
|
|
return no_privacy_practice_enabled && has_no_privacy_practice_info;
|
|
}
|
|
|
|
// Returns true if the Safety Check should display a no off-store extension
|
|
// warning.
|
|
bool SafetyCheckShouldShowOffstoreExtension(
|
|
const Extension& extension,
|
|
Profile* profile,
|
|
const std::optional<CWSInfoService::CWSInfo>& cws_info) {
|
|
if (!base::FeatureList::IsEnabled(
|
|
features::kSafetyHubExtensionsOffStoreTrigger)) {
|
|
return false;
|
|
}
|
|
// Leave command-line extensions out for now since removing them is only
|
|
// effective till the next browser session.
|
|
if (extension.location() == mojom::ManifestLocation::kCommandLine) {
|
|
return false;
|
|
}
|
|
// Calculate if the extension triggers an off-store extension warning such as
|
|
// extensions that are no longer on the Chrome Web Store.
|
|
if (Manifest::IsUnpackedLocation(extension.location())) {
|
|
// Extensions that are unpacked will only trigger a review if dev
|
|
// mode is not enabled.
|
|
bool dev_mode =
|
|
profile->GetPrefs()->GetBoolean(prefs::kExtensionsUIDeveloperMode);
|
|
return !dev_mode;
|
|
} else {
|
|
ExtensionManagement* extension_management =
|
|
ExtensionManagementFactory::GetForBrowserContext(profile);
|
|
bool updates_from_webstore =
|
|
extension_management->UpdatesFromWebstore(extension);
|
|
if (updates_from_webstore) {
|
|
if (cws_info.has_value() && !cws_info->is_present) {
|
|
// If the extension has a webstore update URL but is not present
|
|
// in the webstore itself, then we will not consider it from
|
|
// the webstore.
|
|
return true;
|
|
}
|
|
} else {
|
|
// Extension does not update from the webstore.
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Return the `PrefAcknowledgeSafetyCheckWarningReason` pref as an enum.
|
|
developer::SafetyCheckWarningReason GetPrefAcknowledgeSafetyCheckWarningReason(
|
|
const Extension& extension,
|
|
ExtensionPrefs* extension_prefs) {
|
|
int kept_reason_int = 0;
|
|
extension_prefs->ReadPrefAsInteger(
|
|
extension.id(), extensions::kPrefAcknowledgeSafetyCheckWarningReason,
|
|
&kept_reason_int);
|
|
developer::SafetyCheckWarningReason acknowledged_reason =
|
|
static_cast<developer::SafetyCheckWarningReason>(kept_reason_int);
|
|
return (acknowledged_reason);
|
|
}
|
|
|
|
// Converts the `SafetyCheckWarningReason` enum into its corresponding
|
|
// warning level. The greater the return value the more severe the
|
|
// trigger.
|
|
int GetSafetyCheckWarningLevel(
|
|
developer::SafetyCheckWarningReason safety_check_warning) {
|
|
switch (safety_check_warning) {
|
|
case developer::SafetyCheckWarningReason::kMalware:
|
|
return 6;
|
|
case developer::SafetyCheckWarningReason::kPolicy:
|
|
return 5;
|
|
case developer::SafetyCheckWarningReason::kUnwanted:
|
|
return 4;
|
|
case developer::SafetyCheckWarningReason::kUnpublished:
|
|
return 3;
|
|
case developer::SafetyCheckWarningReason::kNoPrivacyPractice:
|
|
return 2;
|
|
case developer::SafetyCheckWarningReason::kOffstore:
|
|
return 1;
|
|
case developer::SafetyCheckWarningReason::kNone:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Checks if the user has already acknowledged a safety check warning that
|
|
// is of the same or greater level than the current warning reason.
|
|
// * current_warning - Current Safety Check warning reason
|
|
// * acknowledged_warning - Previously acknowledged warning reason
|
|
bool SafetyCheckHasUserAcknowledgedWarningLevel(
|
|
developer::SafetyCheckWarningReason acknowledged_reason,
|
|
developer::SafetyCheckWarningReason warning_reason) {
|
|
int acknowledged_reason_level =
|
|
GetSafetyCheckWarningLevel(acknowledged_reason);
|
|
int warning_reason_level = GetSafetyCheckWarningLevel(warning_reason);
|
|
return acknowledged_reason_level >= warning_reason_level;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace ExtensionSafetyCheckUtils {
|
|
|
|
developer::SafetyCheckWarningReason GetSafetyCheckWarningReason(
|
|
const Extension& extension,
|
|
Profile* profile,
|
|
bool unpublished_only) {
|
|
CWSInfoService* cws_info_service =
|
|
CWSInfoService::Get(Profile::FromBrowserContext(profile));
|
|
BitMapBlocklistState blocklist_state =
|
|
blocklist_prefs::GetExtensionBlocklistState(extension.id(),
|
|
ExtensionPrefs::Get(profile));
|
|
return GetSafetyCheckWarningReasonHelper(
|
|
cws_info_service, blocklist_state, profile, extension, unpublished_only);
|
|
}
|
|
|
|
developer::SafetyCheckWarningReason GetSafetyCheckWarningReasonHelper(
|
|
CWSInfoServiceInterface* cws_info_service,
|
|
BitMapBlocklistState blocklist_state,
|
|
Profile* profile,
|
|
const Extension& extension,
|
|
bool unpublished_only) {
|
|
developer::SafetyCheckWarningReason top_warning_reason =
|
|
developer::SafetyCheckWarningReason::kNone;
|
|
ExtensionManagement* extension_management =
|
|
ExtensionManagementFactory::GetForBrowserContext(profile);
|
|
bool is_extension = extension.is_extension() || extension.is_shared_module();
|
|
bool is_non_visible_extension =
|
|
extensions::Manifest::IsComponentLocation(extension.location());
|
|
bool is_explicitly_allowed_by_policy =
|
|
extension_management->IsInstallationExplicitlyAllowed(extension.id());
|
|
// We do not show warnings for the following:
|
|
// - Chrome apps
|
|
// - Chrome extensions that are enterprise policy controlled OR not visible
|
|
// to the user.
|
|
if (!is_extension || is_non_visible_extension ||
|
|
is_explicitly_allowed_by_policy) {
|
|
return developer::SafetyCheckWarningReason::kNone;
|
|
}
|
|
|
|
developer::SafetyCheckWarningReason acknowledged_reason =
|
|
GetPrefAcknowledgeSafetyCheckWarningReason(extension,
|
|
ExtensionPrefs::Get(profile));
|
|
std::optional<CWSInfoService::CWSInfo> cws_info =
|
|
cws_info_service->GetCWSInfo(extension);
|
|
bool valid_cws_info = cws_info.has_value() && cws_info->is_present;
|
|
if (unpublished_only) {
|
|
if (valid_cws_info && cws_info->unpublished_long_ago) {
|
|
top_warning_reason = developer::SafetyCheckWarningReason::kUnpublished;
|
|
}
|
|
} else {
|
|
if (SafetyCheckShouldShowMalware(blocklist_state, cws_info)) {
|
|
top_warning_reason = developer::SafetyCheckWarningReason::kMalware;
|
|
} else if (SafetyCheckShouldShowPolicyViolation(blocklist_state,
|
|
cws_info)) {
|
|
top_warning_reason = developer::SafetyCheckWarningReason::kPolicy;
|
|
} else if (SafetyCheckShouldShowPotentiallyUnwanted(blocklist_state)) {
|
|
top_warning_reason = developer::SafetyCheckWarningReason::kUnwanted;
|
|
} else if (valid_cws_info && cws_info->unpublished_long_ago) {
|
|
top_warning_reason = developer::SafetyCheckWarningReason::kUnpublished;
|
|
|
|
} else if (SafetyCheckShouldShowNoPrivacyPractice(cws_info)) {
|
|
top_warning_reason =
|
|
developer::SafetyCheckWarningReason::kNoPrivacyPractice;
|
|
|
|
} else if (SafetyCheckShouldShowOffstoreExtension(extension, profile,
|
|
cws_info)) {
|
|
top_warning_reason = developer::SafetyCheckWarningReason::kOffstore;
|
|
}
|
|
}
|
|
|
|
// TODO(crbug.com/325469212) Remove after migration is deemed complete.
|
|
MigrateSafetyCheckAcknowledgePref(extension, acknowledged_reason,
|
|
top_warning_reason,
|
|
ExtensionPrefs::Get(profile));
|
|
|
|
// If user has chosen to keep the extension for the current, or a higher
|
|
// trigger reason, we will return no trigger.
|
|
if (SafetyCheckHasUserAcknowledgedWarningLevel(acknowledged_reason,
|
|
top_warning_reason)) {
|
|
return developer::SafetyCheckWarningReason::kNone;
|
|
}
|
|
return top_warning_reason;
|
|
}
|
|
|
|
api::developer_private::SafetyCheckStrings GetSafetyCheckWarningStrings(
|
|
developer::SafetyCheckWarningReason warning_reason,
|
|
developer::ExtensionState state) {
|
|
developer::SafetyCheckStrings display_strings;
|
|
int detail_string_id = -1;
|
|
int panel_string_id = -1;
|
|
switch (warning_reason) {
|
|
case developer::SafetyCheckWarningReason::kMalware:
|
|
detail_string_id = IDS_SAFETY_CHECK_EXTENSIONS_MALWARE;
|
|
panel_string_id = IDS_EXTENSIONS_SC_MALWARE;
|
|
break;
|
|
case developer::SafetyCheckWarningReason::kPolicy:
|
|
detail_string_id = IDS_SAFETY_CHECK_EXTENSIONS_POLICY_VIOLATION;
|
|
panel_string_id = state == developer::ExtensionState::kEnabled
|
|
? IDS_EXTENSIONS_SC_POLICY_VIOLATION_ON
|
|
: IDS_EXTENSIONS_SC_POLICY_VIOLATION_OFF;
|
|
break;
|
|
case developer::SafetyCheckWarningReason::kUnwanted:
|
|
detail_string_id = IDS_SAFETY_CHECK_EXTENSIONS_POLICY_VIOLATION;
|
|
panel_string_id = state == developer::ExtensionState::kEnabled
|
|
? IDS_EXTENSIONS_SC_POLICY_VIOLATION_ON
|
|
: IDS_EXTENSIONS_SC_POLICY_VIOLATION_OFF;
|
|
break;
|
|
case developer::SafetyCheckWarningReason::kNoPrivacyPractice:
|
|
detail_string_id = IDS_EXTENSIONS_SAFETY_CHECK_NO_PRIVACY_PRACTICES;
|
|
panel_string_id =
|
|
state == developer::ExtensionState::kEnabled
|
|
? IDS_EXTENSIONS_SAFETY_CHECK_NO_PRIVACY_PRACTICES_ON
|
|
: IDS_EXTENSIONS_SAFETY_CHECK_NO_PRIVACY_PRACTICES_OFF;
|
|
break;
|
|
case developer::SafetyCheckWarningReason::kOffstore:
|
|
detail_string_id = IDS_EXTENSIONS_SAFETY_CHECK_OFFSTORE;
|
|
panel_string_id = state == developer::ExtensionState::kEnabled
|
|
? IDS_EXTENSIONS_SAFETY_CHECK_OFFSTORE_ON
|
|
: IDS_EXTENSIONS_SAFETY_CHECK_OFFSTORE_OFF;
|
|
break;
|
|
case developer::SafetyCheckWarningReason::kUnpublished:
|
|
detail_string_id = IDS_SAFETY_CHECK_EXTENSIONS_UNPUBLISHED;
|
|
panel_string_id = state == developer::ExtensionState::kEnabled
|
|
? IDS_EXTENSIONS_SC_UNPUBLISHED_ON
|
|
: IDS_EXTENSIONS_SC_UNPUBLISHED_OFF;
|
|
break;
|
|
case developer::SafetyCheckWarningReason::kNone:
|
|
break;
|
|
}
|
|
if (detail_string_id != -1) {
|
|
display_strings.detail_string = l10n_util::GetStringUTF8(detail_string_id);
|
|
display_strings.panel_string = l10n_util::GetStringUTF8(panel_string_id);
|
|
}
|
|
return display_strings;
|
|
}
|
|
} // namespace ExtensionSafetyCheckUtils
|
|
} // namespace extensions
|