update_engine: Store and test kern/fw versions as 4 values
- Refactors the handling of _firmware_version and _kernel_version
attributes to split as 4x 16 bit values.
- Based on discussion, we are trying to avoid using the client
specific version format in the network protocol.
- Each of firmware and kernel versions will be stored separately
as uint16_t rather than combined as a uint32_t.
- In the Omaha response the two fields of each version type will
be encoded as key_version.version
- Adds tests for the actual parsing of the response.
BUG=chromium:840432
TEST=cros_run_unit_tests --board=samus --packages update_engine
Change-Id: I4da02e3f4715a132db7e96e55c30139fff6fe6ea
Reviewed-on: https://chromium-review.googlesource.com/1123106
Commit-Ready: Marton Hunyady <[email protected]>
Tested-by: Zentaro Kavanagh <[email protected]>
Reviewed-by: Amin Hassani <[email protected]>
diff --git a/common/utils.cc b/common/utils.cc
index b06954b..1a8fd53 100644
--- a/common/utils.cc
+++ b/common/utils.cc
@@ -61,6 +61,7 @@
using base::Time;
using base::TimeDelta;
using std::min;
+using std::numeric_limits;
using std::pair;
using std::string;
using std::vector;
@@ -1082,6 +1083,36 @@
return value;
}
+void ParseRollbackKeyVersion(const string& raw_version,
+ uint16_t* high_version,
+ uint16_t* low_version) {
+ DCHECK(high_version);
+ DCHECK(low_version);
+ *high_version = numeric_limits<uint16_t>::max();
+ *low_version = numeric_limits<uint16_t>::max();
+
+ vector<string> parts = base::SplitString(
+ raw_version, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ if (parts.size() != 2) {
+ // The version string must have exactly one period.
+ return;
+ }
+
+ int high;
+ int low;
+ if (!(base::StringToInt(parts[0], &high) &&
+ base::StringToInt(parts[1], &low))) {
+ // Both parts of the version could not be parsed correctly.
+ return;
+ }
+
+ if (high >= 0 && high < numeric_limits<uint16_t>::max() && low >= 0 &&
+ low < numeric_limits<uint16_t>::max()) {
+ *high_version = static_cast<uint16_t>(high);
+ *low_version = static_cast<uint16_t>(low);
+ }
+}
+
} // namespace utils
} // namespace chromeos_update_engine
diff --git a/common/utils.h b/common/utils.h
index cbc5eb9..ecb97a3 100644
--- a/common/utils.h
+++ b/common/utils.h
@@ -21,6 +21,7 @@
#include <unistd.h>
#include <algorithm>
+#include <limits>
#include <map>
#include <memory>
#include <set>
@@ -322,6 +323,13 @@
// first section of |version| is invalid (e.g. not a number).
int VersionPrefix(const std::string& version);
+// Parses a string in the form high.low, where high and low are 16 bit unsigned
+// integers. If there is more than 1 dot, or if either of the two parts are
+// not valid 16 bit unsigned numbers, then 0xffff is returned for both.
+void ParseRollbackKeyVersion(const std::string& raw_version,
+ uint16_t* high_version,
+ uint16_t* low_version);
+
} // namespace utils
diff --git a/common/utils_unittest.cc b/common/utils_unittest.cc
index 16fa481..1590eeb 100644
--- a/common/utils_unittest.cc
+++ b/common/utils_unittest.cc
@@ -22,6 +22,7 @@
#include <sys/stat.h>
#include <sys/types.h>
+#include <limits>
#include <string>
#include <vector>
@@ -32,6 +33,7 @@
#include "update_engine/common/test_utils.h"
+using std::numeric_limits;
using std::string;
using std::vector;
@@ -450,6 +452,22 @@
*ret = true;
}
+static void ExpectParseRollbackKeyVersion(const string& version,
+ uint16_t expected_high,
+ uint16_t expected_low) {
+ uint16_t actual_high;
+ uint16_t actual_low;
+ utils::ParseRollbackKeyVersion(version, &actual_high, &actual_low);
+ EXPECT_EQ(expected_high, actual_high);
+ EXPECT_EQ(expected_low, actual_low);
+}
+
+static void ExpectInvalidParseRollbackKeyVersion(const string& version) {
+ ExpectParseRollbackKeyVersion(version,
+ numeric_limits<uint16_t>::max(),
+ numeric_limits<uint16_t>::max());
+}
+
TEST(UtilsTest, TestMacros) {
bool void_test = false;
VoidMacroTestHelper(&void_test);
@@ -523,4 +541,29 @@
EXPECT_EQ(-1, utils::VersionPrefix("x.1"));
}
+TEST(UtilsTest, ParseDottedVersion) {
+ // Valid case.
+ ExpectParseRollbackKeyVersion("2.3", 2, 3);
+ ExpectParseRollbackKeyVersion("65535.65535", 65535, 65535);
+
+ // Zero is technically allowed but never actually used.
+ ExpectParseRollbackKeyVersion("0.0", 0, 0);
+
+ // Invalid cases.
+ ExpectInvalidParseRollbackKeyVersion("");
+ ExpectInvalidParseRollbackKeyVersion("2");
+ ExpectInvalidParseRollbackKeyVersion("2.");
+ ExpectInvalidParseRollbackKeyVersion(".2");
+ ExpectInvalidParseRollbackKeyVersion("2.2.");
+ ExpectInvalidParseRollbackKeyVersion("2.2.3");
+ ExpectInvalidParseRollbackKeyVersion(".2.2");
+ ExpectInvalidParseRollbackKeyVersion("a.b");
+ ExpectInvalidParseRollbackKeyVersion("1.b");
+ ExpectInvalidParseRollbackKeyVersion("a.2");
+ ExpectInvalidParseRollbackKeyVersion("65536.65536");
+ ExpectInvalidParseRollbackKeyVersion("99999.99999");
+ ExpectInvalidParseRollbackKeyVersion("99999.1");
+ ExpectInvalidParseRollbackKeyVersion("1.99999");
+}
+
} // namespace chromeos_update_engine
diff --git a/omaha_request_action.cc b/omaha_request_action.cc
index ca6e348..3502048 100644
--- a/omaha_request_action.cc
+++ b/omaha_request_action.cc
@@ -18,6 +18,7 @@
#include <inttypes.h>
+#include <limits>
#include <map>
#include <sstream>
#include <string>
@@ -55,6 +56,7 @@
using base::TimeDelta;
using chromeos_update_manager::kRollforwardInfinity;
using std::map;
+using std::numeric_limits;
using std::string;
using std::vector;
@@ -938,6 +940,20 @@
return true;
}
+// Parses the 2 key version strings kernel_version and firmware_version. If the
+// field is not present, or cannot be parsed the values default to 0xffff.
+void ParseRollbackVersions(OmahaParserData* parser_data,
+ OmahaResponse* output_object) {
+ utils::ParseRollbackKeyVersion(
+ parser_data->updatecheck_attrs[kFirmwareVersion],
+ &output_object->rollback_key_version.firmware_key,
+ &output_object->rollback_key_version.firmware);
+ utils::ParseRollbackKeyVersion(
+ parser_data->updatecheck_attrs[kKernelVersion],
+ &output_object->rollback_key_version.kernel_key,
+ &output_object->rollback_key_version.kernel);
+}
+
} // namespace
bool OmahaRequestAction::ParseResponse(OmahaParserData* parser_data,
@@ -1004,14 +1020,10 @@
// Defaults to false if attribute is not present.
output_object->is_rollback =
ParseBool(parser_data->updatecheck_attrs[kRollback]);
- // Defaults to 0 if attribute is not present. This is fine for these values,
- // because it's the lowest possible value, and the rollback image won't be
- // installed, if the values in the TPM are larger than these, and in case the
- // values in the TPM are 0, all images will be able to boot up.
- output_object->firmware_version = static_cast<uint32_t>(
- ParseInt(parser_data->updatecheck_attrs[kFirmwareVersion]));
- output_object->kernel_version = static_cast<uint32_t>(
- ParseInt(parser_data->updatecheck_attrs[kKernelVersion]));
+
+ // Parses the rollback versions of the current image. If the fields do not
+ // exist they default to 0xffff for the 4 key versions.
+ ParseRollbackVersions(parser_data, output_object);
if (!ParseStatus(parser_data, output_object, completer))
return false;
diff --git a/omaha_request_action_unittest.cc b/omaha_request_action_unittest.cc
index 7c9fe41..21aecb2 100644
--- a/omaha_request_action_unittest.cc
+++ b/omaha_request_action_unittest.cc
@@ -84,6 +84,16 @@
// This is a helper struct to allow unit tests build an update response with the
// values they care about.
struct FakeUpdateResponse {
+ string GetRollbackVersionAttributes() const {
+ return (rollback ? " _rollback=\"true\"" : "") +
+ (!rollback_firmware_version.empty()
+ ? " _firmware_version=\"" + rollback_firmware_version + "\""
+ : "") +
+ (!rollback_kernel_version.empty()
+ ? " _kernel_version=\"" + rollback_kernel_version + "\""
+ : "");
+ }
+
string GetNoUpdateResponse() const {
string entity_str;
if (include_entity)
@@ -121,8 +131,8 @@
"\" cohortname=\"" + cohortname + "\" "
: "") +
" status=\"ok\">"
- "<ping status=\"ok\"/><updatecheck status=\"ok\">"
- "<urls><url codebase=\"" +
+ "<ping status=\"ok\"/><updatecheck status=\"ok\"" +
+ GetRollbackVersionAttributes() + ">" + "<urls><url codebase=\"" +
codebase +
"\"/></urls>"
"<manifest version=\"" +
@@ -220,6 +230,13 @@
bool multi_app_no_update = false;
// Whether to include more than one package in an app.
bool multi_package = false;
+
+ // Whether the payload is a rollback.
+ bool rollback = false;
+ // The verified boot firmware key version for the rollback image.
+ string rollback_firmware_version = "";
+ // The verified boot kernel key version for the rollback image.
+ string rollback_kernel_version = "";
};
} // namespace
@@ -292,7 +309,8 @@
void TestRollbackCheck(bool is_consumer_device,
int rollback_allowed_milestones,
- bool is_policy_loaded);
+ bool is_policy_loaded,
+ OmahaResponse* out_response);
// Runs and checks a ping test. |ping_only| indicates whether it should send
// only a ping or also an updatecheck.
@@ -493,8 +511,8 @@
void OmahaRequestActionTest::TestRollbackCheck(bool is_consumer_device,
int rollback_allowed_milestones,
- bool is_policy_loaded) {
- OmahaResponse response;
+ bool is_policy_loaded,
+ OmahaResponse* out_response) {
fake_update_response_.deadline = "20101020";
ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
-1,
@@ -506,9 +524,9 @@
metrics::CheckResult::kUpdateAvailable,
metrics::CheckReaction::kUpdating,
metrics::DownloadErrorCode::kUnset,
- &response,
+ out_response,
nullptr));
- ASSERT_TRUE(response.update_exists);
+ ASSERT_TRUE(out_response->update_exists);
}
// Tests Event requests -- they should always succeed. |out_post_data|
@@ -2822,9 +2840,11 @@
ReportKeyVersionMetrics(min_kernel_version, min_kernel_version, true))
.Times(1);
+ OmahaResponse response;
TestRollbackCheck(false /* is_consumer_device */,
3 /* rollback_allowed_milestones */,
- false /* is_policy_loaded */);
+ false /* is_policy_loaded */,
+ &response);
// Verify kernel_max_rollforward was set to the current minimum
// kernel key version. This has the effect of freezing roll
@@ -2854,9 +2874,11 @@
ReportKeyVersionMetrics(min_kernel_version, kRollforwardInfinity, true))
.Times(1);
+ OmahaResponse response;
TestRollbackCheck(true /* is_consumer_device */,
3 /* rollback_allowed_milestones */,
- false /* is_policy_loaded */);
+ false /* is_policy_loaded */,
+ &response);
// Verify that with rollback disabled that kernel_max_rollforward
// was set to logical infinity. This is the expected behavior for
@@ -2885,9 +2907,11 @@
ReportKeyVersionMetrics(min_kernel_version, min_kernel_version, true))
.Times(1);
+ OmahaResponse response;
TestRollbackCheck(false /* is_consumer_device */,
allowed_milestones,
- true /* is_policy_loaded */);
+ true /* is_policy_loaded */,
+ &response);
// Verify that with rollback enabled that kernel_max_rollforward
// was set to the current minimum kernel key version. This has
@@ -2917,9 +2941,11 @@
ReportKeyVersionMetrics(min_kernel_version, kRollforwardInfinity, true))
.Times(1);
+ OmahaResponse response;
TestRollbackCheck(false /* is_consumer_device */,
allowed_milestones,
- true /* is_policy_loaded */);
+ true /* is_policy_loaded */,
+ &response);
// Verify that with rollback disabled that kernel_max_rollforward
// was set to logical infinity.
@@ -2927,4 +2953,30 @@
EXPECT_EQ(kRollforwardInfinity, fake_hw->GetMaxKernelKeyRollforward());
}
+TEST_F(OmahaRequestActionTest, RollbackResponseParsedNoEntries) {
+ OmahaResponse response;
+ fake_update_response_.rollback = true;
+ TestRollbackCheck(false /* is_consumer_device */,
+ 4 /* rollback_allowed_milestones */,
+ true /* is_policy_loaded */,
+ &response);
+ EXPECT_TRUE(response.is_rollback);
+}
+
+TEST_F(OmahaRequestActionTest, RollbackResponseValidVersionsParsed) {
+ OmahaResponse response;
+ fake_update_response_.rollback_firmware_version = "1.2";
+ fake_update_response_.rollback_kernel_version = "3.4";
+ fake_update_response_.rollback = true;
+ TestRollbackCheck(false /* is_consumer_device */,
+ 4 /* rollback_allowed_milestones */,
+ true /* is_policy_loaded */,
+ &response);
+ EXPECT_TRUE(response.is_rollback);
+ EXPECT_EQ(1, response.rollback_key_version.firmware_key);
+ EXPECT_EQ(2, response.rollback_key_version.firmware);
+ EXPECT_EQ(3, response.rollback_key_version.kernel_key);
+ EXPECT_EQ(4, response.rollback_key_version.kernel);
+}
+
} // namespace chromeos_update_engine
diff --git a/omaha_response.h b/omaha_response.h
index ef69de2..0ac09df 100644
--- a/omaha_response.h
+++ b/omaha_response.h
@@ -21,6 +21,7 @@
#include <sys/stat.h>
#include <sys/types.h>
+#include <limits>
#include <string>
#include <vector>
@@ -86,12 +87,21 @@
// True if the returned image is a rollback for the device.
bool is_rollback = false;
- // Kernel version of the returned rollback image. 0 if the image is not a
- // rollback.
- uint32_t kernel_version = 0;
- // Firmware version of the returned rollback image. 0 if the image is not a
- // rollback.
- uint32_t firmware_version = 0;
+
+ struct RollbackKeyVersion {
+ // Kernel key version. 0xffff if the value is unknown.
+ uint16_t kernel_key = std::numeric_limits<uint16_t>::max();
+ // Kernel version. 0xffff if the value is unknown.
+ uint16_t kernel = std::numeric_limits<uint16_t>::max();
+ // Firmware key verison. 0xffff if the value is unknown.
+ uint16_t firmware_key = std::numeric_limits<uint16_t>::max();
+ // Firmware version. 0xffff if the value is unknown.
+ uint16_t firmware = std::numeric_limits<uint16_t>::max();
+ };
+
+ // Key versions of the returned rollback image. Values are 0xffff if the
+ // image not a rollback, or the fields were not present.
+ RollbackKeyVersion rollback_key_version;
};
static_assert(sizeof(off_t) == 8, "off_t not 64 bit");
diff --git a/omaha_response_handler_action.cc b/omaha_response_handler_action.cc
index 775c0a8..3a8439d 100644
--- a/omaha_response_handler_action.cc
+++ b/omaha_response_handler_action.cc
@@ -16,6 +16,7 @@
#include "update_engine/omaha_response_handler_action.h"
+#include <limits>
#include <string>
#include <base/logging.h>
@@ -36,6 +37,7 @@
using chromeos_update_manager::Policy;
using chromeos_update_manager::UpdateManager;
+using std::numeric_limits;
using std::string;
namespace chromeos_update_engine {
@@ -149,8 +151,20 @@
system_state_->hardware()->GetMinKernelKeyVersion());
auto min_firmware_key_version = static_cast<uint32_t>(
system_state_->hardware()->GetMinFirmwareKeyVersion());
- if (response.kernel_version < min_kernel_key_version ||
- response.firmware_version < min_firmware_key_version) {
+ uint32_t kernel_key_version =
+ static_cast<uint32_t>(response.rollback_key_version.kernel_key) << 16 |
+ static_cast<uint32_t>(response.rollback_key_version.kernel);
+ uint32_t firmware_key_version =
+ static_cast<uint32_t>(response.rollback_key_version.firmware_key)
+ << 16 |
+ static_cast<uint32_t>(response.rollback_key_version.firmware);
+
+ // Don't attempt a rollback if the versions are incompatible or the
+ // target image does not specify the version information.
+ if (kernel_key_version == numeric_limits<uint32_t>::max() ||
+ firmware_key_version == numeric_limits<uint32_t>::max() ||
+ kernel_key_version < min_kernel_key_version ||
+ firmware_key_version < min_firmware_key_version) {
LOG(ERROR) << "Device won't be able to boot up the rollback image.";
completer.set_code(ErrorCode::kRollbackNotPossible);
return;
diff --git a/omaha_response_handler_action_unittest.cc b/omaha_response_handler_action_unittest.cc
index 7437f50..c598c89 100644
--- a/omaha_response_handler_action_unittest.cc
+++ b/omaha_response_handler_action_unittest.cc
@@ -502,8 +502,10 @@
.size = 1,
.hash = kPayloadHashHex});
in.is_rollback = true;
- in.kernel_version = 0x00010002;
- in.firmware_version = 0x00030004;
+ in.rollback_key_version.kernel = 1;
+ in.rollback_key_version.kernel = 2;
+ in.rollback_key_version.firmware_key = 3;
+ in.rollback_key_version.firmware = 4;
fake_system_state_.fake_hardware()->SetMinKernelKeyVersion(0x00010002);
fake_system_state_.fake_hardware()->SetMinFirmwareKeyVersion(0x00030004);
@@ -524,8 +526,10 @@
.size = 1,
.hash = kPayloadHashHex});
in.is_rollback = true;
- in.kernel_version = 0x00010001; // This is lower than the minimum.
- in.firmware_version = 0x00030004;
+ in.rollback_key_version.kernel_key = 1;
+ in.rollback_key_version.kernel = 1; // This is lower than the minimum.
+ in.rollback_key_version.firmware_key = 3;
+ in.rollback_key_version.firmware = 4;
fake_system_state_.fake_hardware()->SetMinKernelKeyVersion(0x00010002);
fake_system_state_.fake_hardware()->SetMinFirmwareKeyVersion(0x00030004);
@@ -545,8 +549,10 @@
.size = 1,
.hash = kPayloadHashHex});
in.is_rollback = true;
- in.kernel_version = 0x00010002;
- in.firmware_version = 0x00030003; // This is lower than the minimum.
+ in.rollback_key_version.kernel_key = 1;
+ in.rollback_key_version.kernel = 2;
+ in.rollback_key_version.firmware_key = 3;
+ in.rollback_key_version.firmware = 3; // This is lower than the minimum.
fake_system_state_.fake_hardware()->SetMinKernelKeyVersion(0x00010002);
fake_system_state_.fake_hardware()->SetMinFirmwareKeyVersion(0x00030004);