update_engine: Process Omaha response for rollback images

Omaha returns whether the image returned is a rollback in the
_rollback="true" argument. If this is set, the client has to check
whether it's OK to apply the rollback image (policy is specifically
requesting a rollback and verified boot will accept the image based
on its kernel and firmware key versions).

In addition to this, the device has to do a safe powerwash if the
image is a rollback. (We're not supporting rollbacks with partial
or no powerwash yet.)

We're also setting the rollback_happened preference to avoid force
updates happening before the policy is available again.

Chromium CL adding the error code: http://crrev.com/c/1047866

BUG=chromium:840432
TEST='cros_run_unit_tests --board=caroline --packages update_engine'

Change-Id: I1436ca96211b2a8523e78bf83602ef8b6b525570
Reviewed-on: https://chromium-review.googlesource.com/1047610
Commit-Ready: Marton Hunyady <hunyadym@chromium.org>
Tested-by: Marton Hunyady <hunyadym@chromium.org>
Reviewed-by: Amin Hassani <ahassani@chromium.org>
diff --git a/common/error_code.h b/common/error_code.h
index 9e7c71d..c301155 100644
--- a/common/error_code.h
+++ b/common/error_code.h
@@ -77,10 +77,11 @@
   // kPayloadTimestampError = 51,
   kUpdatedButNotActive = 52,
   kNoUpdate = 53,
+  kRollbackNotPossible = 54,
 
   // VERY IMPORTANT! When adding new error codes:
   //
-  // 1) Update tools/metrics/histograms/histograms.xml in Chrome.
+  // 1) Update tools/metrics/histograms/enums.xml in Chrome.
   //
   // 2) Update the assorted switch statements in update_engine which won't
   //    build until this case is added.
diff --git a/common/error_code_utils.cc b/common/error_code_utils.cc
index 6b72eee..a0e75f0 100644
--- a/common/error_code_utils.cc
+++ b/common/error_code_utils.cc
@@ -150,6 +150,8 @@
       return "ErrorCode::kUpdatedButNotActive";
     case ErrorCode::kNoUpdate:
       return "ErrorCode::kNoUpdate";
+    case ErrorCode::kRollbackNotPossible:
+      return "ErrorCode::kRollbackNotPossible";
       // Don't add a default case to let the compiler warn about newly added
       // error codes which should be added here.
   }
diff --git a/metrics_utils.cc b/metrics_utils.cc
index 0ff4cc9..c84aa8f 100644
--- a/metrics_utils.cc
+++ b/metrics_utils.cc
@@ -82,6 +82,7 @@
 
     case ErrorCode::kNewRootfsVerificationError:
     case ErrorCode::kNewKernelVerificationError:
+    case ErrorCode::kRollbackNotPossible:
       return metrics::AttemptResult::kVerificationFailed;
 
     case ErrorCode::kPostinstallRunnerError:
@@ -218,6 +219,7 @@
     case ErrorCode::kOmahaUpdateIgnoredOverCellular:
     case ErrorCode::kUpdatedButNotActive:
     case ErrorCode::kNoUpdate:
+    case ErrorCode::kRollbackNotPossible:
       break;
 
     // Special flags. These can't happen (we mask them out above) but
diff --git a/omaha_request_action.cc b/omaha_request_action.cc
index 6809979..9021e3f 100644
--- a/omaha_request_action.cc
+++ b/omaha_request_action.cc
@@ -88,6 +88,9 @@
 
 // updatecheck attributes (without the underscore prefix).
 static const char* kEolAttr = "eol";
+static const char* kRollback = "rollback";
+static const char* kFirmwareVersion = "firmware_version";
+static const char* kKernelVersion = "kernel_version";
 
 namespace {
 
@@ -996,6 +999,18 @@
 
   // Parse the updatecheck attributes.
   PersistEolStatus(parser_data->updatecheck_attrs);
+  // Rollback-related updatecheck attributes.
+  // 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]));
 
   if (!ParseStatus(parser_data, output_object, completer))
     return false;
diff --git a/omaha_response.h b/omaha_response.h
index b973eb5..e57f291 100644
--- a/omaha_response.h
+++ b/omaha_response.h
@@ -80,6 +80,15 @@
   // PST, according to the Omaha Server's clock and timezone (PST8PDT,
   // aka "Pacific Time".)
   int install_date_days = -1;
+
+  // 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;
 };
 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 f1a3310..3007f29 100644
--- a/omaha_response_handler_action.cc
+++ b/omaha_response_handler_action.cc
@@ -140,6 +140,26 @@
   system_state_->prefs()->SetString(current_channel_key,
                                     params->download_channel());
 
+  // Checking whether device is able to boot up the returned rollback image.
+  if (response.is_rollback) {
+    if (!params->rollback_allowed()) {
+      LOG(ERROR) << "Received rollback image but rollback is not allowed.";
+      completer.set_code(ErrorCode::kOmahaResponseInvalid);
+      return;
+    }
+    auto min_kernel_key_version = static_cast<uint32_t>(
+        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) {
+      LOG(ERROR) << "Device won't be able to boot up the rollback image.";
+      completer.set_code(ErrorCode::kRollbackNotPossible);
+      return;
+    }
+    install_plan_.is_rollback = true;
+  }
+
   if (params->ShouldPowerwash())
     install_plan_.powerwash_required = true;
 
diff --git a/omaha_response_handler_action.h b/omaha_response_handler_action.h
index 2974841..e868b53 100644
--- a/omaha_response_handler_action.h
+++ b/omaha_response_handler_action.h
@@ -89,6 +89,8 @@
   friend class OmahaResponseHandlerActionTest;
 
   FRIEND_TEST(UpdateAttempterTest, CreatePendingErrorEventResumedTest);
+  FRIEND_TEST(UpdateAttempterTest, SetRollbackHappenedNotRollback);
+  FRIEND_TEST(UpdateAttempterTest, SetRollbackHappenedRollback);
   FRIEND_TEST(UpdateAttempterTest, UpdateDeferredByPolicyTest);
 
   DISALLOW_COPY_AND_ASSIGN(OmahaResponseHandlerAction);
diff --git a/omaha_response_handler_action_unittest.cc b/omaha_response_handler_action_unittest.cc
index aba71a2..55c642b 100644
--- a/omaha_response_handler_action_unittest.cc
+++ b/omaha_response_handler_action_unittest.cc
@@ -495,6 +495,103 @@
   EXPECT_TRUE(install_plan.hash_checks_mandatory);
 }
 
+TEST_F(OmahaResponseHandlerActionTest, RollbackTest) {
+  OmahaResponse in;
+  in.update_exists = true;
+  in.packages.push_back({.payload_urls = {"https://RollbackTest"},
+                         .size = 1,
+                         .hash = kPayloadHashHex});
+  in.is_rollback = true;
+  in.kernel_version = 0x00010002;
+  in.firmware_version = 0x00030004;
+
+  fake_system_state_.fake_hardware()->SetMinKernelKeyVersion(0x00010002);
+  fake_system_state_.fake_hardware()->SetMinFirmwareKeyVersion(0x00030004);
+
+  OmahaRequestParams params(&fake_system_state_);
+  params.set_rollback_allowed(true);
+
+  fake_system_state_.set_request_params(&params);
+  InstallPlan install_plan;
+  EXPECT_TRUE(DoTest(in, "", &install_plan));
+  EXPECT_TRUE(install_plan.is_rollback);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, RollbackKernelVersionErrorTest) {
+  OmahaResponse in;
+  in.update_exists = true;
+  in.packages.push_back({.payload_urls = {"https://RollbackTest"},
+                         .size = 1,
+                         .hash = kPayloadHashHex});
+  in.is_rollback = true;
+  in.kernel_version = 0x00010001;  // This is lower than the minimum.
+  in.firmware_version = 0x00030004;
+
+  fake_system_state_.fake_hardware()->SetMinKernelKeyVersion(0x00010002);
+  fake_system_state_.fake_hardware()->SetMinFirmwareKeyVersion(0x00030004);
+
+  OmahaRequestParams params(&fake_system_state_);
+  params.set_rollback_allowed(true);
+
+  fake_system_state_.set_request_params(&params);
+  InstallPlan install_plan;
+  EXPECT_FALSE(DoTest(in, "", &install_plan));
+}
+
+TEST_F(OmahaResponseHandlerActionTest, RollbackFirmwareVersionErrorTest) {
+  OmahaResponse in;
+  in.update_exists = true;
+  in.packages.push_back({.payload_urls = {"https://RollbackTest"},
+                         .size = 1,
+                         .hash = kPayloadHashHex});
+  in.is_rollback = true;
+  in.kernel_version = 0x00010002;
+  in.firmware_version = 0x00030003;  // This is lower than the minimum.
+
+  fake_system_state_.fake_hardware()->SetMinKernelKeyVersion(0x00010002);
+  fake_system_state_.fake_hardware()->SetMinFirmwareKeyVersion(0x00030004);
+
+  OmahaRequestParams params(&fake_system_state_);
+  params.set_rollback_allowed(true);
+
+  fake_system_state_.set_request_params(&params);
+  InstallPlan install_plan;
+  EXPECT_FALSE(DoTest(in, "", &install_plan));
+}
+
+TEST_F(OmahaResponseHandlerActionTest, RollbackNotRollbackTest) {
+  OmahaResponse in;
+  in.update_exists = true;
+  in.packages.push_back({.payload_urls = {"https://RollbackTest"},
+                         .size = 1,
+                         .hash = kPayloadHashHex});
+  in.is_rollback = false;
+
+  OmahaRequestParams params(&fake_system_state_);
+  params.set_rollback_allowed(true);
+
+  fake_system_state_.set_request_params(&params);
+  InstallPlan install_plan;
+  EXPECT_TRUE(DoTest(in, "", &install_plan));
+  EXPECT_FALSE(install_plan.is_rollback);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, RollbackNotAllowedTest) {
+  OmahaResponse in;
+  in.update_exists = true;
+  in.packages.push_back({.payload_urls = {"https://RollbackTest"},
+                         .size = 1,
+                         .hash = kPayloadHashHex});
+  in.is_rollback = true;
+
+  OmahaRequestParams params(&fake_system_state_);
+  params.set_rollback_allowed(false);
+
+  fake_system_state_.set_request_params(&params);
+  InstallPlan install_plan;
+  EXPECT_FALSE(DoTest(in, "", &install_plan));
+}
+
 TEST_F(OmahaResponseHandlerActionTest, SystemVersionTest) {
   OmahaResponse in;
   in.update_exists = true;
diff --git a/payload_consumer/install_plan.h b/payload_consumer/install_plan.h
index 5cdfbc1..929cad3 100644
--- a/payload_consumer/install_plan.h
+++ b/payload_consumer/install_plan.h
@@ -127,6 +127,9 @@
   // False otherwise.
   bool run_post_install{true};
 
+  // True if this update is a rollback.
+  bool is_rollback{false};
+
   // If not blank, a base-64 encoded representation of the PEM-encoded
   // public key in the response.
   std::string public_key_rsa;
diff --git a/payload_consumer/postinstall_runner_action.cc b/payload_consumer/postinstall_runner_action.cc
index cedecda..c672fef 100644
--- a/payload_consumer/postinstall_runner_action.cc
+++ b/payload_consumer/postinstall_runner_action.cc
@@ -57,7 +57,8 @@
   CHECK(HasInputObject());
   install_plan_ = GetInputObject();
 
-  if (install_plan_.powerwash_required) {
+  // Currently we're always powerwashing when rolling back.
+  if (install_plan_.powerwash_required || install_plan_.is_rollback) {
     if (hardware_->SchedulePowerwash()) {
       powerwash_scheduled_ = true;
     } else {
diff --git a/payload_consumer/postinstall_runner_action_unittest.cc b/payload_consumer/postinstall_runner_action_unittest.cc
index a319299..b7f0fdc 100644
--- a/payload_consumer/postinstall_runner_action_unittest.cc
+++ b/payload_consumer/postinstall_runner_action_unittest.cc
@@ -39,6 +39,7 @@
 #include "update_engine/common/fake_hardware.h"
 #include "update_engine/common/test_utils.h"
 #include "update_engine/common/utils.h"
+#include "update_engine/mock_payload_state.h"
 
 using brillo::MessageLoop;
 using chromeos_update_engine::test_utils::ScopedLoopbackDeviceBinder;
@@ -95,9 +96,10 @@
   // Setup an action processor and run the PostinstallRunnerAction with a single
   // partition |device_path|, running the |postinstall_program| command from
   // there.
-  void RunPosinstallAction(const string& device_path,
-                           const string& postinstall_program,
-                           bool powerwash_required);
+  void RunPostinstallAction(const string& device_path,
+                            const string& postinstall_program,
+                            bool powerwash_required,
+                            bool is_rollback);
 
  public:
   void ResumeRunningAction() {
@@ -163,10 +165,11 @@
   ActionProcessor* processor_{nullptr};
 };
 
-void PostinstallRunnerActionTest::RunPosinstallAction(
+void PostinstallRunnerActionTest::RunPostinstallAction(
     const string& device_path,
     const string& postinstall_program,
-    bool powerwash_required) {
+    bool powerwash_required,
+    bool is_rollback) {
   ActionProcessor processor;
   processor_ = &processor;
   ObjectFeederAction<InstallPlan> feeder_action;
@@ -179,6 +182,7 @@
   install_plan.partitions = {part};
   install_plan.download_url = "http://127.0.0.1:8080/update";
   install_plan.powerwash_required = powerwash_required;
+  install_plan.is_rollback = is_rollback;
   feeder_action.set_obj(install_plan);
   PostinstallRunnerAction runner_action(&fake_boot_control_, &fake_hardware_);
   postinstall_action_ = &runner_action;
@@ -241,7 +245,8 @@
 // /postinst command which only exits 0.
 TEST_F(PostinstallRunnerActionTest, RunAsRootSimpleTest) {
   ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
-  RunPosinstallAction(loop.dev(), kPostinstallDefaultScript, false);
+
+  RunPostinstallAction(loop.dev(), kPostinstallDefaultScript, false, false);
   EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
   EXPECT_TRUE(processor_delegate_.processing_done_called_);
 
@@ -251,14 +256,31 @@
 
 TEST_F(PostinstallRunnerActionTest, RunAsRootRunSymlinkFileTest) {
   ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
-  RunPosinstallAction(loop.dev(), "bin/postinst_link", false);
+  RunPostinstallAction(loop.dev(), "bin/postinst_link", false, false);
   EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
 }
 
 TEST_F(PostinstallRunnerActionTest, RunAsRootPowerwashRequiredTest) {
   ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
   // Run a simple postinstall program but requiring a powerwash.
-  RunPosinstallAction(loop.dev(), "bin/postinst_example", true);
+  RunPostinstallAction(loop.dev(),
+                       "bin/postinst_example",
+                       /*powerwash_required=*/true,
+                       false);
+  EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
+
+  // Check that powerwash was scheduled.
+  EXPECT_TRUE(fake_hardware_.IsPowerwashScheduled());
+}
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootRollbackTest) {
+  ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
+
+  // Run a simple postinstall program, rollback happened.
+  RunPostinstallAction(loop.dev(),
+                       "bin/postinst_example",
+                       false,
+                       /*is_rollback=*/true);
   EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
 
   // Check that powerwash was scheduled.
@@ -268,7 +290,7 @@
 // Runs postinstall from a partition file that doesn't mount, so it should
 // fail.
 TEST_F(PostinstallRunnerActionTest, RunAsRootCantMountTest) {
-  RunPosinstallAction("/dev/null", kPostinstallDefaultScript, false);
+  RunPostinstallAction("/dev/null", kPostinstallDefaultScript, false, false);
   EXPECT_EQ(ErrorCode::kPostinstallRunnerError, processor_delegate_.code_);
 
   // In case of failure, Postinstall should not signal a powerwash even if it
@@ -280,7 +302,7 @@
 // fail.
 TEST_F(PostinstallRunnerActionTest, RunAsRootErrScriptTest) {
   ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
-  RunPosinstallAction(loop.dev(), "bin/postinst_fail1", false);
+  RunPostinstallAction(loop.dev(), "bin/postinst_fail1", false, false);
   EXPECT_EQ(ErrorCode::kPostinstallRunnerError, processor_delegate_.code_);
 }
 
@@ -288,7 +310,7 @@
 // UMA with a different error code. Test those cases are properly detected.
 TEST_F(PostinstallRunnerActionTest, RunAsRootFirmwareBErrScriptTest) {
   ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
-  RunPosinstallAction(loop.dev(), "bin/postinst_fail3", false);
+  RunPostinstallAction(loop.dev(), "bin/postinst_fail3", false, false);
   EXPECT_EQ(ErrorCode::kPostinstallBootedFromFirmwareB,
             processor_delegate_.code_);
 }
@@ -296,7 +318,7 @@
 // Check that you can't specify an absolute path.
 TEST_F(PostinstallRunnerActionTest, RunAsRootAbsolutePathNotAllowedTest) {
   ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
-  RunPosinstallAction(loop.dev(), "/etc/../bin/sh", false);
+  RunPostinstallAction(loop.dev(), "/etc/../bin/sh", false, false);
   EXPECT_EQ(ErrorCode::kPostinstallRunnerError, processor_delegate_.code_);
 }
 
@@ -305,7 +327,7 @@
 // SElinux labels are only set on Android.
 TEST_F(PostinstallRunnerActionTest, RunAsRootCheckFileContextsTest) {
   ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
-  RunPosinstallAction(loop.dev(), "bin/self_check_context", false);
+  RunPosinstallAction(loop.dev(), "bin/self_check_context", false, false);
   EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
 }
 #endif  // __ANDROID__
@@ -318,7 +340,7 @@
   loop_.PostTask(FROM_HERE,
                  base::Bind(&PostinstallRunnerActionTest::SuspendRunningAction,
                             base::Unretained(this)));
-  RunPosinstallAction(loop.dev(), "bin/postinst_suspend", false);
+  RunPostinstallAction(loop.dev(), "bin/postinst_suspend", false, false);
   // postinst_suspend returns 0 only if it was suspended at some point.
   EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
   EXPECT_TRUE(processor_delegate_.processing_done_called_);
@@ -330,7 +352,7 @@
 
   // Wait for the action to start and then cancel it.
   CancelWhenStarted();
-  RunPosinstallAction(loop.dev(), "bin/postinst_suspend", false);
+  RunPostinstallAction(loop.dev(), "bin/postinst_suspend", false, false);
   // When canceling the action, the action never finished and therefore we had
   // a ProcessingStopped call instead.
   EXPECT_FALSE(processor_delegate_.code_set_);
@@ -353,7 +375,7 @@
 
   ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
   setup_action_delegate_ = &mock_delegate_;
-  RunPosinstallAction(loop.dev(), "bin/postinst_progress", false);
+  RunPostinstallAction(loop.dev(), "bin/postinst_progress", false, false);
   EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
 }
 
diff --git a/payload_state.cc b/payload_state.cc
index 7c969b3..03f74af 100644
--- a/payload_state.cc
+++ b/payload_state.cc
@@ -359,6 +359,7 @@
     case ErrorCode::kOmahaUpdateIgnoredOverCellular:
     case ErrorCode::kUpdatedButNotActive:
     case ErrorCode::kNoUpdate:
+    case ErrorCode::kRollbackNotPossible:
       LOG(INFO) << "Not incrementing URL index or failure count for this error";
       break;
 
diff --git a/update_attempter.cc b/update_attempter.cc
index db4be6b..4a71e4d 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -1003,6 +1003,12 @@
             payload.metadata_signature + ":";
       }
 
+      // If we just downloaded a rollback image, we should preserve this fact
+      // over the following powerwash.
+      if (install_plan.is_rollback) {
+        system_state_->payload_state()->SetRollbackHappened(true);
+      }
+
       // Expect to reboot into the new version to send the proper metric during
       // next boot.
       system_state_->payload_state()->ExpectRebootInNewVersion(
diff --git a/update_attempter.h b/update_attempter.h
index 4b51478..a3e2b30 100644
--- a/update_attempter.h
+++ b/update_attempter.h
@@ -272,6 +272,8 @@
   FRIEND_TEST(UpdateAttempterTest, RollbackAllowedSetAndReset);
   FRIEND_TEST(UpdateAttempterTest, ScheduleErrorEventActionNoEventTest);
   FRIEND_TEST(UpdateAttempterTest, ScheduleErrorEventActionTest);
+  FRIEND_TEST(UpdateAttempterTest, SetRollbackHappenedNotRollback);
+  FRIEND_TEST(UpdateAttempterTest, SetRollbackHappenedRollback);
   FRIEND_TEST(UpdateAttempterTest, TargetVersionPrefixSetAndReset);
   FRIEND_TEST(UpdateAttempterTest, UpdateAttemptFlagsCachedAtUpdateStart);
   FRIEND_TEST(UpdateAttempterTest, UpdateDeferredByPolicyTest);
diff --git a/update_attempter_unittest.cc b/update_attempter_unittest.cc
index 54a3f2b..3936404 100644
--- a/update_attempter_unittest.cc
+++ b/update_attempter_unittest.cc
@@ -1243,4 +1243,28 @@
   loop_.Run();
 }
 
+TEST_F(UpdateAttempterTest, SetRollbackHappenedRollback) {
+  OmahaResponseHandlerAction* response_action =
+      new OmahaResponseHandlerAction(&fake_system_state_);
+  response_action->install_plan_.is_rollback = true;
+  attempter_.response_handler_action_.reset(response_action);
+
+  EXPECT_CALL(*fake_system_state_.mock_payload_state(),
+              SetRollbackHappened(true))
+      .Times(1);
+  attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess);
+}
+
+TEST_F(UpdateAttempterTest, SetRollbackHappenedNotRollback) {
+  OmahaResponseHandlerAction* response_action =
+      new OmahaResponseHandlerAction(&fake_system_state_);
+  response_action->install_plan_.is_rollback = false;
+  attempter_.response_handler_action_.reset(response_action);
+
+  EXPECT_CALL(*fake_system_state_.mock_payload_state(),
+              SetRollbackHappened(true))
+      .Times(0);
+  attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess);
+}
+
 }  // namespace chromeos_update_engine
diff --git a/update_manager/chromeos_policy.cc b/update_manager/chromeos_policy.cc
index 655ec82..d56a22e 100644
--- a/update_manager/chromeos_policy.cc
+++ b/update_manager/chromeos_policy.cc
@@ -144,6 +144,7 @@
     case ErrorCode::kOmahaUpdateIgnoredOverCellular:
     case ErrorCode::kUpdatedButNotActive:
     case ErrorCode::kNoUpdate:
+    case ErrorCode::kRollbackNotPossible:
       LOG(INFO) << "Not changing URL index or failure count due to error "
                 << chromeos_update_engine::utils::ErrorCodeToString(err_code)
                 << " (" << static_cast<int>(err_code) << ")";