update_engine: Support recovery key version
Parse the recovery key version and send within the request to Omaha
server. This is to distinguish between various recovery keys a device
might be using.
When a recovery key can't be read or is unparsable, the server will be
receiving an empty attribute like: recoverykeyversion=""
Otherwise, a positive integer should be parsed and set within the
`recoverykeyversion` attribute.
It is up to the server to decide what to do when the
`recoverykeyversion` field is missing and make a decision based off
additional fields in the request or simply not serve an update for the
apps that utilize this attribute.
```
INFO update_engine: [omaha_request_action.cc(273)] Request:
<?xml version="1.0" encoding="UTF-8"?>
<request requestid="6c5e5d4b-d3ca-44bc-8f33-ef1d7ed66879"
sessionid="35891a80-d3c3-46c8-9721-c9a9f525360b" protocol="3.0"
updater="ChromeOSUpdateEngine" updaterversion="0.1.0.0"
installsource="ondemandupdate" ismachine="1" recoverykeyversion="1" >
...
</request>
```
BUG=b:267545513
TEST=emerge
TEST=deploy
Change-Id: I9344626c4e505af070a5e5b896652c29e5c0dd7f
Reviewed-on: https://chromium-review.googlesource.com/c/aosp/platform/system/update_engine/+/4222216
Tested-by: Jae Hoon Kim <kimjae@chromium.org>
Commit-Queue: Jae Hoon Kim <kimjae@chromium.org>
Reviewed-by: Julius Werner <jwerner@chromium.org>
Reviewed-by: Yuanpeng Ni <yuanpengni@chromium.org>
diff --git a/common/fake_hardware.h b/common/fake_hardware.h
index f883217..a56eb88 100644
--- a/common/fake_hardware.h
+++ b/common/fake_hardware.h
@@ -145,6 +145,14 @@
return false;
}
+ bool GetRecoveryKeyVersion(std::string* version) override {
+ if (recovery_key_version_.empty()) {
+ return false;
+ }
+ *version = recovery_key_version_;
+ return true;
+ }
+
bool GetPowerwashSafeDirectory(base::FilePath* path) const override {
return false;
}
@@ -230,6 +238,10 @@
void SetWarmReset(bool warm_reset) override { warm_reset_ = warm_reset; }
+ void SetRecoveryKeyVersion(const std::string& version) {
+ recovery_key_version_ = version;
+ }
+
// Getters to verify state.
int GetMaxKernelKeyRollforward() const { return kernel_max_rollforward_; }
@@ -273,6 +285,7 @@
int64_t build_timestamp_{0};
bool first_active_omaha_ping_sent_{false};
bool warm_reset_{false};
+ std::string recovery_key_version_;
mutable std::map<std::string, std::string> partition_timestamps_;
};
diff --git a/common/hardware_interface.h b/common/hardware_interface.h
index 89c9b01..b54434d 100644
--- a/common/hardware_interface.h
+++ b/common/hardware_interface.h
@@ -125,6 +125,10 @@
// directory available, returns false.
virtual bool GetNonVolatileDirectory(base::FilePath* path) const = 0;
+ // Returns the recovery key version that the device is using.
+ // If key is not found or invalid, returns empty string.
+ virtual bool GetRecoveryKeyVersion(std::string* version) = 0;
+
// Store in |path| the path to a non-volatile directory persisted across
// powerwash cycles. In case of an error, such as no directory available,
// returns false.
diff --git a/common/platform_constants.h b/common/platform_constants.h
index a958e93..1caf659 100644
--- a/common/platform_constants.h
+++ b/common/platform_constants.h
@@ -50,6 +50,9 @@
// The stateful directory used by update_engine.
extern const char kNonVolatileDirectory[];
+// Recovery key version file that exists under the non-volatile directory.
+extern const char kRecoveryKeyVersionFileName[];
+
// Options passed to the filesystem when mounting the new partition during
// postinstall.
extern const char kPostinstallMountOptions[];
diff --git a/cros/hardware_chromeos.cc b/cros/hardware_chromeos.cc
index 3e52808..f5d127d 100644
--- a/cros/hardware_chromeos.cc
+++ b/cros/hardware_chromeos.cc
@@ -37,6 +37,7 @@
#include "update_engine/common/hwid_override.h"
#include "update_engine/common/platform_constants.h"
#include "update_engine/common/subprocess.h"
+#include "update_engine/common/system_state.h"
#include "update_engine/common/utils.h"
#include "update_engine/cros/dbus_connection.h"
#if USE_CFM || USE_REPORT_REQUISITION
@@ -110,6 +111,9 @@
} // namespace hardware
+HardwareChromeOS::HardwareChromeOS()
+ : root_("/"), non_volatile_path_(constants::kNonVolatileDirectory) {}
+
void HardwareChromeOS::Init() {
LoadConfig("" /* root_prefix */, IsNormalBootMode());
debugd_proxy_.reset(
@@ -307,7 +311,48 @@
}
bool HardwareChromeOS::GetNonVolatileDirectory(base::FilePath* path) const {
- *path = base::FilePath(constants::kNonVolatileDirectory);
+ *path = non_volatile_path_;
+ return true;
+}
+
+bool HardwareChromeOS::GetRecoveryKeyVersion(std::string* version) {
+ // Returned the cached value to read once per boot if read successfully.
+ if (!recovery_key_version_.empty()) {
+ *version = recovery_key_version_;
+ return true;
+ }
+
+ // Clear for safety.
+ version->clear();
+
+ base::FilePath non_volatile_path;
+ if (!GetNonVolatileDirectory(&non_volatile_path)) {
+ LOG(ERROR) << "Failed to get non-volatile path.";
+ return false;
+ }
+ auto recovery_key_version_path =
+ non_volatile_path.Append(constants::kRecoveryKeyVersionFileName);
+
+ // Use temporary version string to return empty string on read failure.
+ string tmp_version;
+ if (!base::ReadFileToString(recovery_key_version_path, &tmp_version)) {
+ LOG(ERROR) << "Failed to read recovery key version file at: "
+ << recovery_key_version_path.value();
+ return false;
+ }
+ base::TrimWhitespaceASCII(tmp_version, base::TRIM_ALL, &tmp_version);
+
+ // Check that the version is a valid string of integer.
+ int x;
+ if (!base::StringToInt(tmp_version, &x)) {
+ LOG(ERROR) << "Recovery key version file does not hold a valid version: "
+ << tmp_version;
+ return false;
+ }
+
+ // Only perfect conversions above return true, so safe to return the string
+ // itself without using `NumberToString(...)` or alike.
+ *version = tmp_version;
return true;
}
diff --git a/cros/hardware_chromeos.h b/cros/hardware_chromeos.h
index 089fe0d..339dce3 100644
--- a/cros/hardware_chromeos.h
+++ b/cros/hardware_chromeos.h
@@ -33,7 +33,7 @@
// process.
class HardwareChromeOS final : public HardwareInterface {
public:
- HardwareChromeOS() : root_("/") {}
+ HardwareChromeOS();
HardwareChromeOS(const HardwareChromeOS&) = delete;
HardwareChromeOS& operator=(const HardwareChromeOS&) = delete;
@@ -62,6 +62,7 @@
bool SchedulePowerwash(bool save_rollback_data) override;
bool CancelPowerwash() override;
bool GetNonVolatileDirectory(base::FilePath* path) const override;
+ bool GetRecoveryKeyVersion(std::string* version) override;
bool GetPowerwashSafeDirectory(base::FilePath* path) const override;
int64_t GetBuildTimestamp() const override;
bool AllowDowngrade() const override { return false; }
@@ -77,6 +78,9 @@
const std::string& new_version) const override;
void SetRootForTest(base::FilePath test_root) { root_ = test_root; }
+ void SetNonVolatileDirectoryForTest(const base::FilePath& path) {
+ non_volatile_path_ = path;
+ }
private:
friend class HardwareChromeOSTest;
@@ -88,7 +92,10 @@
bool is_oobe_enabled_;
+ std::string recovery_key_version_;
+
base::FilePath root_;
+ base::FilePath non_volatile_path_;
std::unique_ptr<org::chromium::debugdProxyInterface> debugd_proxy_;
};
diff --git a/cros/hardware_chromeos_unittest.cc b/cros/hardware_chromeos_unittest.cc
index 83425bd..96797e8 100644
--- a/cros/hardware_chromeos_unittest.cc
+++ b/cros/hardware_chromeos_unittest.cc
@@ -26,6 +26,7 @@
#include "update_engine/common/constants.h"
#include "update_engine/common/fake_hardware.h"
+#include "update_engine/common/platform_constants.h"
#include "update_engine/common/test_utils.h"
#include "update_engine/update_manager/umtest_utils.h"
@@ -193,4 +194,66 @@
EXPECT_FALSE(hardware_.IsRunningFromMiniOs());
}
+TEST_F(HardwareChromeOSTest, RecoveryKeyVersionMissingFile) {
+ base::FilePath test_path = root_dir_.GetPath();
+ hardware_.SetNonVolatileDirectoryForTest(test_path);
+
+ base::FilePath non_volatile_directory;
+ ASSERT_TRUE(hardware_.GetNonVolatileDirectory(&non_volatile_directory));
+ ASSERT_TRUE(base::CreateDirectory(non_volatile_directory));
+
+ std::string version;
+ EXPECT_FALSE(hardware_.GetRecoveryKeyVersion(&version));
+}
+
+TEST_F(HardwareChromeOSTest, RecoveryKeyVersionBadKey) {
+ base::FilePath test_path = root_dir_.GetPath();
+ hardware_.SetNonVolatileDirectoryForTest(test_path);
+
+ base::FilePath non_volatile_directory;
+ ASSERT_TRUE(hardware_.GetNonVolatileDirectory(&non_volatile_directory));
+ ASSERT_TRUE(base::CreateDirectory(non_volatile_directory));
+
+ EXPECT_TRUE(base::WriteFile(
+ non_volatile_directory.Append(constants::kRecoveryKeyVersionFileName),
+ "foobar"));
+
+ std::string version;
+ EXPECT_FALSE(hardware_.GetRecoveryKeyVersion(&version));
+}
+
+TEST_F(HardwareChromeOSTest, RecoveryKeyVersion) {
+ base::FilePath test_path = root_dir_.GetPath();
+ hardware_.SetNonVolatileDirectoryForTest(test_path);
+
+ base::FilePath non_volatile_directory;
+ ASSERT_TRUE(hardware_.GetNonVolatileDirectory(&non_volatile_directory));
+ ASSERT_TRUE(base::CreateDirectory(non_volatile_directory));
+
+ EXPECT_TRUE(base::WriteFile(
+ non_volatile_directory.Append(constants::kRecoveryKeyVersionFileName),
+ "123"));
+
+ std::string version;
+ EXPECT_TRUE(hardware_.GetRecoveryKeyVersion(&version));
+ EXPECT_EQ(std::string("123"), version);
+}
+
+TEST_F(HardwareChromeOSTest, RecoveryKeyVersionTrimWhitespaces) {
+ base::FilePath test_path = root_dir_.GetPath();
+ hardware_.SetNonVolatileDirectoryForTest(test_path);
+
+ base::FilePath non_volatile_directory;
+ ASSERT_TRUE(hardware_.GetNonVolatileDirectory(&non_volatile_directory));
+ ASSERT_TRUE(base::CreateDirectory(non_volatile_directory));
+
+ EXPECT_TRUE(base::WriteFile(
+ non_volatile_directory.Append(constants::kRecoveryKeyVersionFileName),
+ "\n888\n"));
+
+ std::string version;
+ EXPECT_TRUE(hardware_.GetRecoveryKeyVersion(&version));
+ EXPECT_EQ(std::string("888"), version);
+}
+
} // namespace chromeos_update_engine
diff --git a/cros/omaha_request_builder_xml.cc b/cros/omaha_request_builder_xml.cc
index c61491a..612761d 100644
--- a/cros/omaha_request_builder_xml.cc
+++ b/cros/omaha_request_builder_xml.cc
@@ -434,20 +434,28 @@
string OmahaRequestBuilderXml::GetRequest() const {
auto* system_state = SystemState::Get();
const auto* params = system_state->request_params();
+
string os_xml = GetOs();
string app_xml = GetApps();
string hw_xml = GetHw();
+ // Valid recovery keys that will be sent are "" or "[0-9]+".
+ string recovery_key_version;
+ if (!system_state->hardware()->GetRecoveryKeyVersion(&recovery_key_version)) {
+ LOG(ERROR) << "Failed to get recovery key version.";
+ }
string request_xml = base::StringPrintf(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<request requestid=\"%s\" sessionid=\"%s\""
" protocol=\"3.0\" updater=\"%s\" updaterversion=\"%s\""
- " installsource=\"%s\" ismachine=\"1\" %s>\n%s%s%s</request>\n",
+ " installsource=\"%s\" ismachine=\"1\" recoverykeyversion=\"%s\" "
+ "%s>\n%s%s%s</request>\n",
base::GenerateGUID().c_str() /* requestid */,
session_id_.c_str(),
constants::kOmahaUpdaterID,
kOmahaUpdaterVersion,
params->interactive() ? "ondemandupdate" : "scheduler",
+ recovery_key_version.c_str(),
(system_state->hardware()->IsRunningFromMiniOs() ? "isminios=\"1\"" : ""),
os_xml.c_str(),
app_xml.c_str(),
diff --git a/cros/omaha_request_builder_xml_unittest.cc b/cros/omaha_request_builder_xml_unittest.cc
index e96388b..1892748 100644
--- a/cros/omaha_request_builder_xml_unittest.cc
+++ b/cros/omaha_request_builder_xml_unittest.cc
@@ -187,6 +187,23 @@
EXPECT_EQ(gen_session_id, session_id);
}
+TEST_F(OmahaRequestBuilderXmlTest, GetRecoveryKeyVersionMissing) {
+ FakeSystemState::Get()->fake_hardware()->SetRecoveryKeyVersion("");
+ OmahaRequestBuilderXml omaha_request{nullptr, false, false, 0, 0, 0, ""};
+ const string request_xml = omaha_request.GetRequest();
+ EXPECT_EQ(1, CountSubstringInString(request_xml, "recoverykeyversion=\"\""))
+ << request_xml;
+}
+
+TEST_F(OmahaRequestBuilderXmlTest, GetRecoveryKeyVersion) {
+ FakeSystemState::Get()->fake_hardware()->SetRecoveryKeyVersion("123");
+ OmahaRequestBuilderXml omaha_request{nullptr, false, false, 0, 0, 0, ""};
+ const string request_xml = omaha_request.GetRequest();
+ const string recovery_key_version =
+ FindAttributeKeyValueInXml(request_xml, "recoverykeyversion", 3);
+ EXPECT_EQ("123", recovery_key_version) << request_xml;
+}
+
TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlPlatformUpdateTest) {
OmahaRequestBuilderXml omaha_request{nullptr, false, false, 0, 0, 0, ""};
const string request_xml = omaha_request.GetRequest();
diff --git a/cros/platform_constants_chromeos.cc b/cros/platform_constants_chromeos.cc
index cf3f0c3..e760293 100644
--- a/cros/platform_constants_chromeos.cc
+++ b/cros/platform_constants_chromeos.cc
@@ -31,6 +31,7 @@
const char kCACertificatesPath[] = "/usr/share/chromeos-ca-certificates";
// This directory is wiped during powerwash.
const char kNonVolatileDirectory[] = "/var/lib/update_engine";
+const char kRecoveryKeyVersionFileName[] = "recovery_key_version";
const char kPostinstallMountOptions[] = "";
} // namespace constants
diff --git a/init/update-engine.conf b/init/update-engine.conf
index 6fd51ee..8a03fb0 100644
--- a/init/update-engine.conf
+++ b/init/update-engine.conf
@@ -38,6 +38,9 @@
# impact system responsiveness.
exec ionice -c3 update_engine
+env REC_KEY_READABLE=/var/lib/update_engine/recovery_key_readable
+env REC_KEY_VERSION=/var/lib/update_engine/recovery_key_version
+
# Put update_engine process in its own cgroup.
# Default cpu.shares is 1024.
post-start script
@@ -54,4 +57,19 @@
mkdir -p "${cgroup_net_cls_dir}"
echo ${pid} > "${cgroup_net_cls_dir}/tasks"
echo "0x10001" > "${cgroup_net_cls_dir}/net_cls.classid"
+
+ # Run this everytime on boot instead of caching and preserving because
+ # it's possible that the values will change if device is reflashed.
+ recovery_key_tmp="$(mktemp)"
+ flashrom -i GBB:"${recovery_key_tmp}" -r
+ if [ "$?" -ne 0 ]; then
+ logger -t "${UPSTART_JOB}" "Failed to read flashrom."
+ unlink "${recovery_key_tmp}"
+ exit 0
+ fi
+ futility show "${recovery_key_tmp}" > "${REC_KEY_READABLE}"
+ grep -A3 'Recovery Key:' "${REC_KEY_READABLE}" \
+ | grep 'Key Version:' \
+ | grep -Eo '[0-9]+' > "${REC_KEY_VERSION}"
+ unlink "${recovery_key_tmp}"
end script