update_engine: Fix enrollment recovery mode logic
Retrieve EnrollmentRecoveryRequired from Local state and ignore
non-critical update if enrollment recovery mode is set to true.
BUG=chromium:1355305
TEST=Run all the update_engines tests
Change-Id: I2e3f59087662f94fa0afa825e5513dde3352c9ac
Reviewed-on: https://chromium-review.googlesource.com/c/aosp/platform/system/update_engine/+/3940187
Reviewed-by: Roman Sorokin <rsorokin@google.com>
Reviewed-by: Jae Hoon Kim <kimjae@chromium.org>
Commit-Queue: Brahim Chikhaoui <bchikhaoui@google.com>
Reviewed-by: Miriam Polzer <mpolzer@google.com>
Tested-by: Brahim Chikhaoui <bchikhaoui@google.com>
diff --git a/common/error_code.h b/common/error_code.h
index 26bd1a9..e9e1156 100644
--- a/common/error_code.h
+++ b/common/error_code.h
@@ -91,6 +91,7 @@
kInvalidateLastUpdate = 65,
kOmahaUpdateIgnoredOverMetered = 66,
kScaledInstallationError = 67,
+ kNonCriticalUpdateEnrollmentRecovery = 68,
// VERY IMPORTANT! When adding new error codes:
//
diff --git a/common/error_code_utils.cc b/common/error_code_utils.cc
index 8af51c2..39f495c 100644
--- a/common/error_code_utils.cc
+++ b/common/error_code_utils.cc
@@ -183,6 +183,8 @@
return "ErrorCode::kOmahaUpdateIgnoredOverMetered";
case ErrorCode::kScaledInstallationError:
return "ErrorCode::kScaledInstallationError";
+ case ErrorCode::kNonCriticalUpdateEnrollmentRecovery:
+ return "ErrorCode::kNonCriticalUpdateEnrollmentRecovery";
// Don't add a default case to let the compiler warn about newly added
// error codes which should be added here.
}
diff --git a/common/fake_hardware.h b/common/fake_hardware.h
index e4abb01..f883217 100644
--- a/common/fake_hardware.h
+++ b/common/fake_hardware.h
@@ -18,9 +18,11 @@
#define UPDATE_ENGINE_COMMON_FAKE_HARDWARE_H_
#include <map>
+#include <memory>
#include <string>
#include <utility>
+#include <base/json/json_string_value_serializer.h>
#include <base/time/time.h>
#include "update_engine/common/error_code.h"
@@ -104,6 +106,20 @@
return true;
}
+ bool IsEnrollmentRecoveryModeEnabled(
+ const base::Value* local_state) const override {
+ return is_enrollment_recovery_enabled_;
+ }
+
+ std::unique_ptr<base::Value> ReadLocalState() const override {
+ int error_code;
+ std::string error_msg;
+
+ JSONStringValueDeserializer deserializer(local_state_contents_);
+
+ return deserializer.Deserialize(&error_code, &error_msg);
+ }
+
bool SetMaxKernelKeyRollforward(int kernel_max_rollforward) override {
kernel_max_rollforward_ = kernel_max_rollforward;
return true;
@@ -180,6 +196,14 @@
void UnsetIsOOBEComplete() { is_oobe_complete_ = false; }
+ void SetIsEnrollmentRecoveryMode(bool enrollment_recovery) {
+ is_enrollment_recovery_enabled_ = enrollment_recovery;
+ }
+
+ void SetLocalState(std::string local_state) {
+ local_state_contents_ = local_state;
+ }
+
void SetHardwareClass(const std::string& hardware_class) {
hardware_class_ = hardware_class;
}
@@ -233,6 +257,8 @@
bool are_dev_features_enabled_{false};
bool is_oobe_enabled_{true};
bool is_oobe_complete_{true};
+ bool is_enrollment_recovery_enabled_{false};
+ std::string local_state_contents_;
// Jan 20, 2007
base::Time oobe_timestamp_{base::Time::FromTimeT(1169280000)};
std::string hardware_class_{"Fake HWID BLAH-1234"};
diff --git a/common/hardware_interface.h b/common/hardware_interface.h
index 645f929..89c9b01 100644
--- a/common/hardware_interface.h
+++ b/common/hardware_interface.h
@@ -19,11 +19,13 @@
#include <stdint.h>
+#include <memory>
#include <string>
#include <vector>
#include <base/files/file_path.h>
#include <base/time/time.h>
+#include <base/values.h>
#include "update_engine/common/error_code.h"
@@ -71,6 +73,14 @@
// not have a requisition, or if not running Chrome OS.
virtual std::string GetDeviceRequisition() const = 0;
+ // Returns the Local State root as base::Value
+ virtual std::unique_ptr<base::Value> ReadLocalState() const = 0;
+
+ // Returns true if enrollment recovery mode is set on
+ // given Local State
+ virtual bool IsEnrollmentRecoveryModeEnabled(
+ const base::Value* local_state) const = 0;
+
// Returns the minimum kernel key version that verified boot on Chrome OS
// will allow to boot. This is the value of crossystem tpm_kernver. Returns
// -1 on error, or if not running on Chrome OS.
diff --git a/cros/hardware_chromeos.cc b/cros/hardware_chromeos.cc
index 4e74793..3e52808 100644
--- a/cros/hardware_chromeos.cc
+++ b/cros/hardware_chromeos.cc
@@ -20,6 +20,7 @@
#include <base/files/file_path.h>
#include <base/files/file_util.h>
+#include <base/json/json_file_value_serializer.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_util.h>
@@ -91,6 +92,9 @@
const char kRunningFromMiniOSLabel[] = "cros_minios";
+constexpr char kLocalStatePath[] = "/home/chronos/Local State";
+
+constexpr char kEnrollmentRecoveryRequired[] = "EnrollmentRecoveryRequired";
} // namespace
namespace chromeos_update_engine {
@@ -209,8 +213,7 @@
string HardwareChromeOS::GetDeviceRequisition() const {
#if USE_CFM || USE_REPORT_REQUISITION
- const char* kLocalStatePath = "/home/chronos/Local State";
- return ReadDeviceRequisition(base::FilePath(kLocalStatePath));
+ return ReadDeviceRequisition(ReadLocalState().get());
#else
return "";
#endif
@@ -379,6 +382,46 @@
return true;
}
+std::unique_ptr<base::Value> HardwareChromeOS::ReadLocalState() const {
+ base::FilePath local_state_file = base::FilePath(kLocalStatePath);
+
+ JSONFileValueDeserializer deserializer(local_state_file);
+
+ int error_code;
+ std::string error_msg;
+ std::unique_ptr<base::Value> root =
+ deserializer.Deserialize(&error_code, &error_msg);
+
+ if (!root) {
+ if (error_code != 0) {
+ LOG(ERROR) << "Unable to deserialize Local State with exit code: "
+ << error_code << " and error: " << error_msg;
+ }
+ return nullptr;
+ }
+
+ return root;
+}
+
+// Check for given given Local State the value of the enrollment
+// recovery mode. Returns true if Recoverymode is set on CrOS.
+bool HardwareChromeOS::IsEnrollmentRecoveryModeEnabled(
+ const base::Value* local_state) const {
+ if (!local_state) {
+ return false;
+ }
+
+ auto* path = local_state->FindPath(kEnrollmentRecoveryRequired);
+
+ if (!path || !path->is_bool()) {
+ LOG(INFO) << "EnrollmentRecoveryRequired path does not exist in"
+ << "Local State or is incorrectly formatted.";
+ return false;
+ }
+
+ return path->GetBool();
+}
+
int HardwareChromeOS::GetActiveMiniOsPartition() const {
char value_buffer[VB_MAX_STRING_PROPERTY];
const char* rv = VbGetSystemPropertyString(
diff --git a/cros/hardware_chromeos.h b/cros/hardware_chromeos.h
index fe7b77d..089fe0d 100644
--- a/cros/hardware_chromeos.h
+++ b/cros/hardware_chromeos.h
@@ -50,6 +50,9 @@
bool IsOOBEComplete(base::Time* out_time_of_oobe) const override;
std::string GetHardwareClass() const override;
std::string GetDeviceRequisition() const override;
+ std::unique_ptr<base::Value> ReadLocalState() const override;
+ bool IsEnrollmentRecoveryModeEnabled(
+ const base::Value* local_state) const override;
int GetMinKernelKeyVersion() const override;
int GetMinFirmwareKeyVersion() const override;
int GetMaxFirmwareKeyRollforward() const override;
diff --git a/cros/hardware_chromeos_unittest.cc b/cros/hardware_chromeos_unittest.cc
index 872e7e5..83425bd 100644
--- a/cros/hardware_chromeos_unittest.cc
+++ b/cros/hardware_chromeos_unittest.cc
@@ -20,6 +20,7 @@
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
+#include <base/json/json_string_value_serializer.h>
#include <brillo/file_utils.h>
#include <gtest/gtest.h>
@@ -31,6 +32,30 @@
using chromeos_update_engine::test_utils::WriteFileString;
using std::string;
+namespace {
+
+constexpr char kEnrollmentReCoveryTrueJSON[] = R"({
+ "the_list": [ "val1", "val2" ],
+ "EnrollmentRecoveryRequired": true,
+ "some_String": "1337",
+ "some_int": 42
+})";
+
+constexpr char kEnrollmentReCoveryFalseJSON[] = R"({
+ "the_list": [ "val1", "val2" ],
+ "EnrollmentRecoveryRequired": false,
+ "some_String": "1337",
+ "some_int": 42
+})";
+
+constexpr char kNoEnrollmentRecoveryJSON[] = R"({
+ "the_list": [ "val1", "val2" ],
+ "some_String": "1337",
+ "some_int": 42
+})";
+
+} // namespace
+
namespace chromeos_update_engine {
class HardwareChromeOSTest : public ::testing::Test {
@@ -56,10 +81,43 @@
hardware_.LoadConfig(root_dir_.GetPath().value(), normal_mode);
}
+ std::unique_ptr<base::Value> JSONToUniquePtrValue(const string& json_string) {
+ int error_code;
+ std::string error_msg;
+
+ JSONStringValueDeserializer deserializer(json_string);
+
+ return deserializer.Deserialize(&error_code, &error_msg);
+ }
+
HardwareChromeOS hardware_;
base::ScopedTempDir root_dir_;
};
+TEST_F(HardwareChromeOSTest, NoLocalFile) {
+ std::unique_ptr<base::Value> root = nullptr;
+
+ EXPECT_FALSE(hardware_.IsEnrollmentRecoveryModeEnabled(root.get()));
+}
+
+TEST_F(HardwareChromeOSTest, LocalFileWithEnrollmentRecoveryTrue) {
+ std::unique_ptr<base::Value> root =
+ JSONToUniquePtrValue(kEnrollmentReCoveryTrueJSON);
+ EXPECT_TRUE(hardware_.IsEnrollmentRecoveryModeEnabled(root.get()));
+}
+
+TEST_F(HardwareChromeOSTest, LocalFileWithEnrollmentRecoveryFalse) {
+ std::unique_ptr<base::Value> root =
+ JSONToUniquePtrValue(kEnrollmentReCoveryFalseJSON);
+ EXPECT_FALSE(hardware_.IsEnrollmentRecoveryModeEnabled(root.get()));
+}
+
+TEST_F(HardwareChromeOSTest, LocalFileWithNoEnrollmentRecoveryPath) {
+ std::unique_ptr<base::Value> root =
+ JSONToUniquePtrValue(kNoEnrollmentRecoveryJSON);
+ EXPECT_FALSE(hardware_.IsEnrollmentRecoveryModeEnabled(root.get()));
+}
+
TEST_F(HardwareChromeOSTest, NoFileFoundReturnsDefault) {
CallLoadConfig(true /* normal_mode */);
EXPECT_TRUE(hardware_.IsOOBEEnabled());
diff --git a/cros/omaha_request_action.cc b/cros/omaha_request_action.cc
index 388eda8..6708e5a 100644
--- a/cros/omaha_request_action.cc
+++ b/cros/omaha_request_action.cc
@@ -63,6 +63,9 @@
namespace chromeos_update_engine {
namespace {
+
+constexpr char kCriticalAppVersion[] = "ForcedUpdate";
+
// Parses |str| and returns |true| iff its value is "true".
bool ParseBool(const string& str) {
return str == "true";
@@ -1240,8 +1243,9 @@
}
bool OmahaRequestAction::ShouldIgnoreUpdate(ErrorCode* error) const {
+ const auto* hardware = SystemState::Get()->hardware();
// Never ignore valid update when running from MiniOS.
- if (SystemState::Get()->hardware()->IsRunningFromMiniOs())
+ if (hardware->IsRunningFromMiniOs())
return false;
// Note: policy decision to not update to a version we rolled back from.
@@ -1261,16 +1265,25 @@
return true;
}
- if (SystemState::Get()->hardware()->IsOOBEEnabled() &&
- !SystemState::Get()->hardware()->IsOOBEComplete(nullptr) &&
+ if (hardware->IsOOBEEnabled() && !hardware->IsOOBEComplete(nullptr) &&
(response_.deadline.empty() ||
SystemState::Get()->payload_state()->GetRollbackHappened()) &&
- params->app_version() != "ForcedUpdate") {
+ params->app_version() != kCriticalAppVersion) {
LOG(INFO) << "Ignoring a non-critical Omaha update before OOBE completion.";
*error = ErrorCode::kNonCriticalUpdateInOOBE;
return true;
}
+ if (hardware->IsEnrollmentRecoveryModeEnabled(
+ hardware->ReadLocalState().get()) &&
+ response_.deadline.empty() &&
+ params->app_version() != kCriticalAppVersion) {
+ LOG(INFO) << "Ignoring non-critical Omaha update as enrollment "
+ << "recovery mode is enabled.";
+ *error = ErrorCode::kNonCriticalUpdateEnrollmentRecovery;
+ return true;
+ }
+
if (params->is_install()) {
LOG(INFO) << "Skipping connection type check for DLC installations.";
} else if (!IsUpdateAllowedOverCurrentConnection(error)) {
diff --git a/cros/omaha_request_action_unittest.cc b/cros/omaha_request_action_unittest.cc
index e4b8588..0e6b49d 100644
--- a/cros/omaha_request_action_unittest.cc
+++ b/cros/omaha_request_action_unittest.cc
@@ -1128,6 +1128,29 @@
}
// Verify that non-critical updates are skipped by reporting the
+// kNonCriticalUpdateEnrollmentRecovery error code.
+TEST_F(OmahaRequestActionTest, SkipNonCriticalUpdatesEnrollmentRecoveryMode) {
+ FakeSystemState::Get()->fake_hardware()->SetIsEnrollmentRecoveryMode(true);
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ tuc_params_.expected_code = ErrorCode::kNonCriticalUpdateEnrollmentRecovery;
+ tuc_params_.expected_check_result = metrics::CheckResult::kParsingError;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ ASSERT_FALSE(TestUpdateCheck());
+
+ EXPECT_FALSE(response_.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, ValidUpdateForNonEnrollmentRecoveryMode) {
+ FakeSystemState::Get()->fake_hardware()->SetIsEnrollmentRecoveryMode(false);
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_TRUE(response_.update_exists);
+}
+
+// Verify that non-critical updates are skipped by reporting the
// kNonCriticalUpdateInOOBE error code when attempted over cellular network -
// i.e. when the update would need user permission. Note that reporting
// kOmahaUpdateIgnoredOverCellular error in this case might cause undesired UX
diff --git a/cros/payload_state.cc b/cros/payload_state.cc
index 007bef8..1937fb9 100644
--- a/cros/payload_state.cc
+++ b/cros/payload_state.cc
@@ -380,6 +380,7 @@
case ErrorCode::kInvalidateLastUpdate:
case ErrorCode::kOmahaUpdateIgnoredOverMetered:
case ErrorCode::kScaledInstallationError:
+ case ErrorCode::kNonCriticalUpdateEnrollmentRecovery:
LOG(INFO) << "Not incrementing URL index or failure count for this error";
break;
diff --git a/cros/requisition_util.cc b/cros/requisition_util.cc
index 9ac5740..12f2654 100644
--- a/cros/requisition_util.cc
+++ b/cros/requisition_util.cc
@@ -39,7 +39,7 @@
namespace chromeos_update_engine {
-string ReadDeviceRequisition(const base::FilePath& local_state) {
+string ReadDeviceRequisition(const base::Value* local_state) {
string requisition;
bool vpd_retval = utils::GetVpdValue(kOemRequisitionKey, &requisition);
@@ -49,20 +49,8 @@
// OR
// 2. Requisition value mistakenly set to "none".
if ((requisition.empty() || requisition == kNoRequisition || !vpd_retval) &&
- base::PathExists(local_state)) {
- int error_code;
- std::string error_msg;
- JSONFileValueDeserializer deserializer(local_state);
- std::unique_ptr<base::Value> root =
- deserializer.Deserialize(&error_code, &error_msg);
- if (!root) {
- if (error_code != 0) {
- LOG(ERROR) << "Unable to deserialize Local State with exit code: "
- << error_code << " and error: " << error_msg;
- }
- return "";
- }
- auto* path = root->FindPath({"enrollment", "device_requisition"});
+ local_state) {
+ auto* path = local_state->FindPath({"enrollment", "device_requisition"});
if (!path || !path->is_string()) {
return "";
}
diff --git a/cros/requisition_util.h b/cros/requisition_util.h
index 6ec4783..4203157 100644
--- a/cros/requisition_util.h
+++ b/cros/requisition_util.h
@@ -17,15 +17,17 @@
#ifndef UPDATE_ENGINE_CROS_REQUISITION_UTIL_H_
#define UPDATE_ENGINE_CROS_REQUISITION_UTIL_H_
+#include <memory>
#include <string>
#include <base/files/file_path.h>
+#include <base/values.h>
namespace chromeos_update_engine {
// Checks the VPD and Local State for the device's requisition and returns it,
// or an empty string if the device has no requisition.
-std::string ReadDeviceRequisition(const base::FilePath& local_state);
+std::string ReadDeviceRequisition(const base::Value* local_state);
} // namespace chromeos_update_engine
diff --git a/cros/requisition_util_unittest.cc b/cros/requisition_util_unittest.cc
index 269585e..c22fb8e 100644
--- a/cros/requisition_util_unittest.cc
+++ b/cros/requisition_util_unittest.cc
@@ -17,10 +17,12 @@
#include "update_engine/cros/requisition_util.h"
#include <string>
+#include <utility>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
+#include <base/json/json_string_value_serializer.h>
#include <gtest/gtest.h>
#include "update_engine/common/test_utils.h"
@@ -59,36 +61,34 @@
class RequisitionUtilTest : public ::testing::Test {
protected:
- void SetUp() override { ASSERT_TRUE(root_dir_.CreateUniqueTempDir()); }
+ std::unique_ptr<base::Value> JsonToUniquePtrValue(const string& json_string) {
+ int error_code;
+ std::string error_msg;
- void WriteJsonToFile(const string& json) {
- path_ =
- base::FilePath(root_dir_.GetPath().value() + "/chronos/Local State");
- ASSERT_TRUE(base::CreateDirectory(path_.DirName()));
- ASSERT_TRUE(WriteFileString(path_.value(), json));
+ JSONStringValueDeserializer deserializer(json_string);
+
+ return deserializer.Deserialize(&error_code, &error_msg);
}
-
- base::ScopedTempDir root_dir_;
- base::FilePath path_;
};
TEST_F(RequisitionUtilTest, BadJsonReturnsEmpty) {
- WriteJsonToFile("this isn't JSON");
- EXPECT_EQ("", ReadDeviceRequisition(path_));
+ std::unique_ptr<base::Value> root = JsonToUniquePtrValue("this isn't JSON");
+ EXPECT_EQ("", ReadDeviceRequisition(root.get()));
}
TEST_F(RequisitionUtilTest, NoFileReturnsEmpty) {
- EXPECT_EQ("", ReadDeviceRequisition(path_));
+ std::unique_ptr<base::Value> root = nullptr;
+ EXPECT_EQ("", ReadDeviceRequisition(root.get()));
}
TEST_F(RequisitionUtilTest, EnrollmentRequisition) {
- WriteJsonToFile(kRemoraJSON);
- EXPECT_EQ("remora", ReadDeviceRequisition(path_));
+ std::unique_ptr<base::Value> root = JsonToUniquePtrValue(kRemoraJSON);
+ EXPECT_EQ("remora", ReadDeviceRequisition(root.get()));
}
TEST_F(RequisitionUtilTest, BlankEnrollment) {
- WriteJsonToFile(kNoEnrollmentJSON);
- EXPECT_EQ("", ReadDeviceRequisition(path_));
+ std::unique_ptr<base::Value> root = JsonToUniquePtrValue(kNoEnrollmentJSON);
+ EXPECT_EQ("", ReadDeviceRequisition(root.get()));
}
} // namespace chromeos_update_engine
diff --git a/metrics_utils.cc b/metrics_utils.cc
index 7b21afa..6196d98 100644
--- a/metrics_utils.cc
+++ b/metrics_utils.cc
@@ -129,6 +129,7 @@
case ErrorCode::kOmahaUpdateDeferredPerPolicy:
case ErrorCode::kNonCriticalUpdateInOOBE:
case ErrorCode::kDownloadCancelledPerPolicy:
+ case ErrorCode::kNonCriticalUpdateEnrollmentRecovery:
case ErrorCode::kRepeatedFpFromOmahaError:
return metrics::AttemptResult::kUpdateSkipped;
@@ -252,6 +253,7 @@
case ErrorCode::kInvalidateLastUpdate:
case ErrorCode::kOmahaUpdateIgnoredOverMetered:
case ErrorCode::kScaledInstallationError:
+ case ErrorCode::kNonCriticalUpdateEnrollmentRecovery:
break;
// Special flags. These can't happen (we mask them out above) but
diff --git a/update_manager/update_can_start_policy.cc b/update_manager/update_can_start_policy.cc
index 1fd62b4..164d1d6 100644
--- a/update_manager/update_can_start_policy.cc
+++ b/update_manager/update_can_start_policy.cc
@@ -146,6 +146,7 @@
case ErrorCode::kInvalidateLastUpdate:
case ErrorCode::kOmahaUpdateIgnoredOverMetered:
case ErrorCode::kScaledInstallationError:
+ case ErrorCode::kNonCriticalUpdateEnrollmentRecovery:
LOG(INFO) << "Not changing URL index or failure count due to error "
<< chromeos_update_engine::utils::ErrorCodeToString(err_code)
<< " (" << static_cast<int>(err_code) << ")";