// Copyright 2023 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/ash/report_controller_initializer/report_controller_initializer.h"
#include <memory>
#include <string>
#include <unordered_set>
#include <utility>
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "chrome/browser/ash/login/startup_utils.h"
#include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
#include "chrome/browser/ash/settings/device_settings_service.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part_ash.h"
#include "chrome/browser/first_run/first_run.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/common/channel_info.h"
#include "chromeos/ash/components/report/device_metrics/use_case/real_psm_client_manager.h"
#include "chromeos/ash/components/report/proto/fresnel_service.pb.h"
#include "chromeos/ash/components/report/utils/time_utils.h"
#include "chromeos/ash/components/settings/cros_settings.h"
#include "chromeos/ash/components/settings/cros_settings_provider.h"
#include "components/policy/core/common/cloud/cloud_policy_constants.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
namespace ash {
namespace {
// Path to file storing the last powerwash time, persisted over safe powerwash.
constexpr char kLastPowerwashTimePath[] =
"/mnt/stateful_partition/unencrypted/preserve/last_powerwash_time";
// Number of minutes to wait before retrying
// reading the .oobe_completed file again.
constexpr base::TimeDelta kOobeReadFailedRetryDelay = base::Minutes(60);
// Number of times to retry before failing to report any device actives.
constexpr int kNumberOfRetriesBeforeFail = 120;
// Record the state transitions for the |ReportInitializer| class.
void RecordInitializerState(ReportControllerInitializer::State state) {
base::UmaHistogramEnumeration("Ash.Report.InitializerState", state);
}
// Record minutes of startup delay before reporting.
void RecordStartupDelay(int delay_minutes) {
base::UmaHistogramCustomCounts("Ash.Report.StartupDelay", delay_minutes,
/*min=*/0,
/*exclusive_max=*/60, /*buckets=*/61);
}
// Record whether oobe is completed.
void RecordIsOobeCompleted(bool is_complete) {
base::UmaHistogramBoolean("Ash.Report.IsOobeCompleted", is_complete);
}
void RecordLastPowerwashTimeRead(bool success) {
base::UmaHistogramBoolean("Ash.Report.IsLastPowerwashTimeRead", success);
}
// Record the device trusted status enum when checking policy trusted status.
void RecordTrustedStatus(CrosSettingsProvider::TrustedStatus status) {
ReportControllerInitializer::TrustedStatus status_mapped;
switch (status) {
case CrosSettingsProvider::TrustedStatus::PERMANENTLY_UNTRUSTED:
status_mapped =
ReportControllerInitializer::TrustedStatus::kPermanentlyUntrusted;
break;
case CrosSettingsProvider::TrustedStatus::TEMPORARILY_UNTRUSTED:
status_mapped =
ReportControllerInitializer::TrustedStatus::kTemporarilyUntrusted;
break;
case CrosSettingsProvider::TrustedStatus::TRUSTED:
status_mapped = ReportControllerInitializer::TrustedStatus::kTrusted;
break;
}
base::UmaHistogramEnumeration("Ash.Report.TrustedStatus", status_mapped);
}
// Record the device market segment after oobe completed and segment is ready.
// @param market_segment Defined in fresnel_service.proto
void RecordMarketSegment(report::MarketSegment market_segment) {
ReportControllerInitializer::MarketSegment market_segment_mapped;
switch (market_segment) {
case report::MarketSegment::MARKET_SEGMENT_UNSPECIFIED:
market_segment_mapped =
ReportControllerInitializer::MarketSegment::kUnspecified;
break;
case report::MarketSegment::MARKET_SEGMENT_UNKNOWN:
market_segment_mapped =
ReportControllerInitializer::MarketSegment::kUnspecified;
break;
case report::MarketSegment::MARKET_SEGMENT_CONSUMER:
market_segment_mapped =
ReportControllerInitializer::MarketSegment::kConsumer;
break;
case report::MarketSegment::MARKET_SEGMENT_ENTERPRISE_ENROLLED_BUT_UNKNOWN:
market_segment_mapped = ReportControllerInitializer::MarketSegment::
kEnterpriseEnrolledButUnknown;
break;
case report::MarketSegment::MARKET_SEGMENT_ENTERPRISE:
market_segment_mapped =
ReportControllerInitializer::MarketSegment::kEnterprise;
break;
case report::MarketSegment::MARKET_SEGMENT_EDUCATION:
market_segment_mapped =
ReportControllerInitializer::MarketSegment::kEducation;
break;
case report::MarketSegment::MARKET_SEGMENT_ENTERPRISE_DEMO:
market_segment_mapped =
ReportControllerInitializer::MarketSegment::kEnterpriseDemo;
break;
}
base::UmaHistogramEnumeration("Ash.Report.MarketSegment",
market_segment_mapped);
}
// Determine market segment from the loaded ChromeOS device policies.
report::MarketSegment GetMarketSegment(
policy::DeviceMode device_mode,
policy::MarketSegment device_market_segment) {
// Policy device modes that should be classified as not being set.
const std::unordered_set<policy::DeviceMode> kDeviceModeNotSet{
policy::DeviceMode::DEVICE_MODE_PENDING,
policy::DeviceMode::DEVICE_MODE_NOT_SET};
// Policy device modes that should be classified as consumer devices.
const std::unordered_set<policy::DeviceMode> kDeviceModeConsumer{
policy::DeviceMode::DEVICE_MODE_CONSUMER};
// Policy device modes that should be classified as enterprise devices.
const std::unordered_set<policy::DeviceMode> kDeviceModeEnterprise{
policy::DeviceMode::DEVICE_MODE_ENTERPRISE};
// Policy device modes that should be classified as demo devices.
const std::unordered_set<policy::DeviceMode> kDeviceModeDemoEnterprise{
policy::DeviceMode::DEVICE_MODE_DEMO};
// Determine Fresnel market segment using the retrieved device policy
// |device_mode| and |device_market_segment|.
if (kDeviceModeNotSet.count(device_mode)) {
return report::MARKET_SEGMENT_UNKNOWN;
}
if (kDeviceModeConsumer.count(device_mode)) {
return report::MARKET_SEGMENT_CONSUMER;
}
if (kDeviceModeDemoEnterprise.count(device_mode)) {
return report::MARKET_SEGMENT_ENTERPRISE_DEMO;
}
if (kDeviceModeEnterprise.count(device_mode)) {
if (device_market_segment == policy::MarketSegment::ENTERPRISE) {
return report::MARKET_SEGMENT_ENTERPRISE;
}
if (device_market_segment == policy::MarketSegment::EDUCATION) {
return report::MARKET_SEGMENT_EDUCATION;
}
return report::MARKET_SEGMENT_ENTERPRISE_ENROLLED_BUT_UNKNOWN;
}
return report::MARKET_SEGMENT_UNKNOWN;
}
// Reads the last powerwash time from preserved files. If the device is new
// or the last powerwash time file does not exist, it will return UnixEpoch.
base::Time ReadLastPowerwashTime() {
// Retrieve the last modified time of the powerwash time file.
base::FilePath last_powerwash_file(kLastPowerwashTimePath);
base::File::Info info;
if (!base::GetFileInfo(last_powerwash_file, &info)) {
LOG(ERROR) << "Failed to get last powerwash file info.";
return base::Time::UnixEpoch();
}
return info.last_modified;
}
} // namespace
ReportControllerInitializer::ReportControllerInitializer() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
SetState(State::kWaitingForOwnership);
// Adds observer for device ownership status changes in this class.
device_settings_observation_.Observe(DeviceSettingsService::Get());
OwnershipStatusChanged();
}
ReportControllerInitializer::~ReportControllerInitializer() = default;
void ReportControllerInitializer::SetState(State state) {
state_ = state;
RecordInitializerState(state_);
}
report::MarketSegment ReportControllerInitializer::GetMarketSegmentForTesting(
const policy::DeviceMode& device_mode,
const policy::MarketSegment& device_market_segment) {
return GetMarketSegment(device_mode, device_market_segment);
}
void ReportControllerInitializer::OwnershipStatusChanged() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (state_ != State::kWaitingForOwnership) {
LOG(ERROR) << "Invalid state - expected to be waiting for ownership.";
return;
}
// Device should only get ownership taken at most once on a browser start up.
if (ash::DeviceSettingsService::Get()->GetOwnershipStatus() !=
ash::DeviceSettingsService::OwnershipStatus::kOwnershipTaken) {
LOG(ERROR) << "Ownership status is not taken yet, returning early.";
return;
}
SetState(State::kWaitingForStartupDelay);
// Retrieve chrome first run sentinel time.
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
base::BindOnce(&first_run::GetFirstRunSentinelCreationTime),
base::BindOnce(
&ReportControllerInitializer::OnFirstRunSentinelCreationTimeRead,
weak_factory_.GetWeakPtr()));
}
void ReportControllerInitializer::OnFirstRunSentinelCreationTimeRead(
base::Time first_chrome_run_time) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(state_, State::kWaitingForStartupDelay);
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(
&ReportControllerInitializer::CheckOobeCompleted,
weak_factory_.GetWeakPtr(),
base::BindRepeating(&StartupUtils::GetTimeSinceOobeFlagFileCreation)),
DetermineStartUpDelay(first_chrome_run_time));
}
base::TimeDelta ReportControllerInitializer::DetermineStartUpDelay(
base::Time chrome_first_run_ts) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(state_, State::kWaitingForStartupDelay);
// Wait at least 1 hour from the first chrome run sentinel file creation
// time. This creation time is used as an indicator of when the device last
// reset (powerwash/recovery/RMA). PSM servers can take 1 hour after CheckIn
// to return the correct response for CheckMembership requests, since the PSM
// servers need to update their cache.
//
// This delay avoids the scenario where a device checks in, powerwashes, and
// on device start up, gets the wrong check membership response.
base::TimeDelta delay_on_first_chrome_run;
base::Time current_ts = base::Time::Now();
if (current_ts < (chrome_first_run_ts + base::Hours(1))) {
delay_on_first_chrome_run =
chrome_first_run_ts + base::Hours(1) - current_ts;
}
RecordStartupDelay(delay_on_first_chrome_run.InMinutes());
return delay_on_first_chrome_run;
}
void ReportControllerInitializer::CheckOobeCompleted(
base::RepeatingCallback<base::TimeDelta()> check_oobe_completed_callback) {
SetState(State::kWaitingForOobeCompleted);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// We block if the oobe completed file is not written.
// ChromeOS devices should go through oobe to be considered a real device.
// The ActivateDate is also only set after oobe is written.
if (retry_oobe_completed_count_ >= kNumberOfRetriesBeforeFail) {
LOG(ERROR) << "Retry failed - .oobe_completed file was not written for "
<< "1 minute after retrying 120 times. "
<< "There was a 60 minute wait between each retry and spanned "
<< "5 days.";
RecordIsOobeCompleted(false);
return;
}
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
base::BindOnce(check_oobe_completed_callback),
base::BindOnce(&ReportControllerInitializer::OnOobeFileWritten,
weak_factory_.GetWeakPtr(),
check_oobe_completed_callback));
}
void ReportControllerInitializer::OnOobeFileWritten(
base::RepeatingCallback<base::TimeDelta()> check_oobe_completed_callback,
base::TimeDelta time_since_oobe_file_written) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(state_, State::kWaitingForOobeCompleted);
// If the OOBE completed file isn't created yet,
// time_since_oobe_file_written returns base::TimeDelta().
if (time_since_oobe_file_written == base::TimeDelta() ||
time_since_oobe_file_written < base::Minutes(1)) {
++retry_oobe_completed_count_;
LOG(ERROR) << "Time since oobe file created was less than 1 minute. "
<< "Wait and retry again after 1 minute to ensure that "
<< "the ActivateDate VPD field is set. "
<< "TimeDelta since oobe flag file was created = "
<< time_since_oobe_file_written
<< ". Retry count = " << retry_oobe_completed_count_;
RecordIsOobeCompleted(false);
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&ReportControllerInitializer::CheckOobeCompleted,
weak_factory_.GetWeakPtr(),
std::move(check_oobe_completed_callback)),
kOobeReadFailedRetryDelay);
return;
}
RecordIsOobeCompleted(true);
CheckTrustedStatus();
}
void ReportControllerInitializer::CheckTrustedStatus() {
SetState(State::kWaitingForDeviceSettingsTrusted);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(state_, State::kWaitingForDeviceSettingsTrusted);
// Device is owned, confirm the settings can be trusted.
CrosSettingsProvider::TrustedStatus status =
CrosSettings::Get()->PrepareTrustedValues(
base::BindOnce(&ReportControllerInitializer::CheckTrustedStatus,
weak_factory_.GetWeakPtr()));
// Record histogram that indicates the status of the device policies.
RecordTrustedStatus(status);
if (status == CrosSettingsProvider::TEMPORARILY_UNTRUSTED ||
status == CrosSettingsProvider::PERMANENTLY_UNTRUSTED) {
// When status is TEMPORARILY_UNTRUSTED, PrepareTrustedValues method takes
// ownership of the start report controller callback.
// It will retry later when the TRUSTED status becomes available.
//
// When status is PERMANENTLY_UNTRUSTED, client assumes this status is final
// until browser restarts. Client does not proceed without signature
// verification, so retry is not attempted. This status may be caused
// if the policy proto blob fails the signature check.
LOG(ERROR) << "CrosSettings status is not trusted yet.";
return;
}
SetState(State::kWaitingForLastPowerwashTime);
// Retrieve last powerwash time, if file exists.
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
base::BindOnce(&ReadLastPowerwashTime),
base::BindOnce(&ReportControllerInitializer::OnLastPowerwashTimeRead,
weak_factory_.GetWeakPtr()));
}
void ReportControllerInitializer::OnLastPowerwashTimeRead(
base::Time last_powerwash_gmt) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(state_, State::kWaitingForLastPowerwashTime);
// Default values before handling last powerwash time.
// Variable is based off GMT YYYY-WW just like ActivateDate VPD field.
std::string last_powerwash_week;
// Handle the last powerwash time received after read attempt.
if (last_powerwash_gmt.is_null() ||
last_powerwash_gmt == base::Time::UnixEpoch()) {
RecordLastPowerwashTimeRead(false);
} else {
last_powerwash_week =
report::utils::ConvertTimeToISO8601String(last_powerwash_gmt);
RecordLastPowerwashTimeRead(true);
}
// OOBE is completed, so we can safely calculate the device market
// segment.
report::MarketSegment device_market_segment =
GetMarketSegment(g_browser_process->platform_part()
->browser_policy_connector_ash()
->GetDeviceMode(),
g_browser_process->platform_part()
->browser_policy_connector_ash()
->GetEnterpriseMarketSegment());
// Record histogram after oobe is completed and the policies are in trusted
// status. At this point, the device market segment is known and assigned.
RecordMarketSegment(device_market_segment);
SetState(State::kReportControllerInitialized);
// At this step we have checked for 3 conditions.
// 1. The device is owned.
// 2. OOBE is completed and .oobe_completed file is written > 1 minute ago.
// 3. CrosSettingsProvider::TRUSTED: device policies are loaded and trusted.
report_controller_ = std::make_unique<report::ReportController>(
ash::report::device_metrics::ChromeDeviceMetadataParameters{
chrome::GetChannel() /* chromeos_channel */, device_market_segment,
last_powerwash_week},
g_browser_process->local_state(),
g_browser_process->system_network_context_manager()
->GetSharedURLLoaderFactory(),
std::make_unique<ash::report::device_metrics::PsmClientManager>(
std::make_unique<
report::device_metrics::RealPsmClientManagerDelegate>()));
}
} // namespace ash