update_engine: Use the rootfs size specified by verity.

When using rootfs verification and generating a payload, it is important
to generate a payload that writes all the blocks verity expects to hash
in the hash tree, even if those blocks are never used by the filesystem.

When using squashfs, the filesystem is padded with zeros up to the
fs_size provided in the disk_layout.json, which is used as the size
of the dm-verity device. Because of this, it is important to always
generate a payload that writes all that.

This patch parses the kernel using vboot_host tools and updates the
rootfs_size value with the one specified to verity in the kernel
command line. When no verity options are found or the kernel is not
provided (as in the case of a full kernel update in a delta payload)
only the filesystem part is considered for the rootfs_size. This
means that the extra zeros in the source rootfs won't be used for
the delta payload when generating a full kernel payload, and also
means that the zeros after the squashfs won't be written when
generating the new rootfs with rootfs verification disabled.

BUG=chromium:463783
TEST=FEATURES=test emerge-link update_engine
TEST=Ran delta_generator with invalind kernels (in full and delta mode).
TEST=Ran cros_generate_update_payload with real images with both
squashfs and ext2.

Change-Id: Id151063722a20d27c50724f6b27f774a3436e3ea
Reviewed-on: https://chromium-review.googlesource.com/259839
Reviewed-by: Alex Vakulenko <avakulenko@chromium.org>
Commit-Queue: Alex Deymo <deymo@chromium.org>
Trybot-Ready: Alex Deymo <deymo@chromium.org>
Tested-by: Alex Deymo <deymo@chromium.org>
Reviewed-by: Don Garrett <dgarrett@chromium.org>
diff --git a/payload_generator/generate_delta_main.cc b/payload_generator/generate_delta_main.cc
index fb79a2f..2cb8de2 100644
--- a/payload_generator/generate_delta_main.cc
+++ b/payload_generator/generate_delta_main.cc
@@ -60,7 +60,6 @@
   }
 }
 
-
 bool ParseImageInfo(const string& channel,
                     const string& board,
                     const string& version,
@@ -406,6 +405,11 @@
     payload_config.chunk_size = 1024 * 1024;
   }
 
+  // Load the rootfs size from verity's kernel command line if rootfs
+  // verification is enabled.
+  payload_config.source.LoadVerityRootfsSize();
+  payload_config.target.LoadVerityRootfsSize();
+
   if (payload_config.is_delta) {
     LOG(INFO) << "Generating delta update";
   } else {
@@ -414,7 +418,8 @@
 
   // From this point, all the options have been parsed.
   if (!payload_config.Validate()) {
-    LOG(FATAL) << "Invalid options passed. See errors above.";
+    LOG(ERROR) << "Invalid options passed. See errors above.";
+    return 1;
   }
 
   uint64_t metadata_size;
diff --git a/payload_generator/payload_generation_config.cc b/payload_generator/payload_generation_config.cc
index 956f362..4249b9e 100644
--- a/payload_generator/payload_generation_config.cc
+++ b/payload_generator/payload_generation_config.cc
@@ -4,8 +4,11 @@
 
 #include "update_engine/payload_generator/payload_generation_config.h"
 
+#include <base/logging.h>
+
 #include "update_engine/delta_performer.h"
 #include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/verity_utils.h"
 #include "update_engine/utils.h"
 
 namespace chromeos_update_engine {
@@ -46,7 +49,7 @@
   TEST_AND_RETURN_FALSE(utils::GetFilesystemSize(rootfs_part,
                                                  &rootfs_block_count,
                                                  &rootfs_block_size));
-  rootfs_size = static_cast<size_t>(rootfs_block_count) * rootfs_block_size;
+  rootfs_size = static_cast<uint64_t>(rootfs_block_count) * rootfs_block_size;
   if (!kernel_part.empty())
     kernel_size = utils::FileSize(kernel_part);
 
@@ -61,6 +64,24 @@
   return true;
 }
 
+bool ImageConfig::LoadVerityRootfsSize() {
+  if (kernel_part.empty())
+    return false;
+  uint64_t verity_rootfs_size = 0;
+  if (!GetVerityRootfsSize(kernel_part, &verity_rootfs_size)) {
+    LOG(INFO) << "Couldn't find verity options in source kernel config, will "
+              << "use the rootfs filesystem size instead: " << rootfs_size;
+    return false;
+  }
+  if (rootfs_size != verity_rootfs_size) {
+    LOG(WARNING) << "Using the rootfs size found in the kernel config ("
+                 << verity_rootfs_size << ") instead of the rootfs filesystem "
+                 << " size (" << rootfs_size << ").";
+    rootfs_size = verity_rootfs_size;
+  }
+  return true;
+}
+
 bool ImageConfig::ImageInfoIsEmpty() const {
   return image_info.board().empty()
     && image_info.key().empty()
diff --git a/payload_generator/payload_generation_config.h b/payload_generator/payload_generation_config.h
index 616148a..02bd8d4 100644
--- a/payload_generator/payload_generation_config.h
+++ b/payload_generator/payload_generation_config.h
@@ -33,6 +33,13 @@
   // Returns whether the image size was properly detected.
   bool LoadImageSize();
 
+  // Load the |rootfs_size| stored in the kernel command line in the
+  // |kernel_part| when the kernel is using rootfs verification (dm-verity).
+  // Returns whether it loaded the size from the kernel command line. For
+  // example, it would return false if no |kernel_part| was provided or the
+  // kernel doesn't have verity enabled.
+  bool LoadVerityRootfsSize();
+
   // Returns whether the |image_info| field is empty.
   bool ImageInfoIsEmpty() const;
 
diff --git a/payload_generator/verity_utils.cc b/payload_generator/verity_utils.cc
new file mode 100644
index 0000000..c9198e0
--- /dev/null
+++ b/payload_generator/verity_utils.cc
@@ -0,0 +1,127 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/verity_utils.h"
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <chromeos/strings/string_utils.h>
+extern "C" {
+#include <vboot/vboot_host.h>
+}
+
+using std::string;
+using std::vector;
+
+extern "C" {
+
+// vboot_host.h has a default VbExError() that will call exit() when a function
+// fails. We redefine that function here so it doesn't exit.
+void VbExError(const char* format, ...) {
+  va_list ap;
+  va_start(ap, format);
+  fprintf(stderr, "ERROR: ");
+  va_end(ap);
+}
+
+}
+
+namespace {
+
+// Splits a string with zero or more arguments separated by spaces into a list
+// of strings, but respecting the double quotes. For example, the string:
+//   a="foo" b=foo c="bar baz"   "my dir"/"my file"
+// has only four arguments, since some parts are grouped together due to the
+// double quotes.
+vector<string> SplitQuotedArgs(const string arglist) {
+  vector<string> terms = chromeos::string_utils::Split(
+      arglist, " ", false, false);
+  vector<string> result;
+  string last_term;
+  size_t quotes = 0;
+  for (const string& term : terms) {
+    if (quotes % 2 == 0 && term.empty())
+      continue;
+
+    quotes += std::count(term.begin(), term.end(), '"');
+    if (last_term.empty()) {
+      last_term = term;
+    } else {
+      last_term += " " + term;
+    }
+    if (quotes % 2 == 0) {
+      result.push_back(last_term);
+      last_term.clear();
+      quotes = 0;
+    }
+  }
+  // Unterminated quoted string found.
+  if (!last_term.empty())
+    result.push_back(last_term);
+  return result;
+}
+
+}  // namespace
+
+namespace chromeos_update_engine {
+
+bool ParseVerityRootfsSize(const string& kernel_cmdline,
+                           uint64_t* rootfs_size) {
+  vector<string> kernel_args = SplitQuotedArgs(kernel_cmdline);
+
+  for (const string& arg : kernel_args) {
+    std::pair<string, string> key_value =
+        chromeos::string_utils::SplitAtFirst(arg, "=", true);
+    if (key_value.first != "dm")
+      continue;
+    string value = key_value.second;
+    if (value.size() > 1 && value.front() == '"' && value.back() == '"')
+      value = value.substr(1, value.size() - 1);
+
+    vector<string> dm_parts = SplitQuotedArgs(value);
+    // Check if this is a dm-verity device.
+    if (std::find(dm_parts.begin(), dm_parts.end(), "verity") == dm_parts.end())
+      continue;
+    for (const string& dm_part : dm_parts) {
+      key_value = chromeos::string_utils::SplitAtFirst(dm_part, "=", true);
+      if (key_value.first != "hashstart")
+        continue;
+      if (!base::StringToUint64(key_value.second, rootfs_size))
+        continue;
+      // The hashstart= value is specified in 512-byte blocks, so we need to
+      // convert that to bytes.
+      *rootfs_size *= 512;
+      return true;
+    }
+  }
+  return false;
+}
+
+bool GetVerityRootfsSize(const string& kernel_dev, uint64_t* rootfs_size) {
+  string kernel_cmdline;
+  char *config = FindKernelConfig(kernel_dev.c_str(), USE_PREAMBLE_LOAD_ADDR);
+  if (!config) {
+    LOG(WARNING) << "Error retrieving kernel command line from '"
+                 << kernel_dev << "', ignoring.";
+    return false;
+  }
+  kernel_cmdline = string(config, MAX_KERNEL_CONFIG_SIZE);
+
+  // FindKernelConfig() expects the caller to free the char*.
+  free(config);
+
+  if (!ParseVerityRootfsSize(kernel_cmdline, rootfs_size)) {
+    LOG(INFO) << "Didn't find the rootfs size in the kernel command line: "
+              << kernel_cmdline;
+    return false;
+  }
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/verity_utils.h b/payload_generator/verity_utils.h
new file mode 100644
index 0000000..3f7b297
--- /dev/null
+++ b/payload_generator/verity_utils.h
@@ -0,0 +1,19 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_VERITY_UTILS_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_VERITY_UTILS_H_
+
+#include <string>
+
+namespace chromeos_update_engine {
+
+bool GetVerityRootfsSize(const std::string& kernel_dev, uint64_t* rootfs_size);
+
+bool ParseVerityRootfsSize(const std::string& kernel_cmdline,
+                           uint64_t* rootfs_size);
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_VERITY_UTILS_H_
diff --git a/payload_generator/verity_utils_unittest.cc b/payload_generator/verity_utils_unittest.cc
new file mode 100644
index 0000000..d1e54e6
--- /dev/null
+++ b/payload_generator/verity_utils_unittest.cc
@@ -0,0 +1,61 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/verity_utils.h"
+
+#include <gtest/gtest.h>
+
+namespace chromeos_update_engine {
+
+// A real kernel command line found on a device.
+static const char* kVerityKernelCommandLine =
+    "console= loglevel=7 init=/sbin/init cros_secure oops=panic panic=-1 "
+    "root=/dev/dm-0 rootwait ro dm_verity.error_behavior=3 "
+    "dm_verity.max_bios=-1 dm_verity.dev_wait=1 "
+    "dm=\"1 vroot none ro 1,0 1536000 verity payload=PARTUUID=%U/PARTNROFF=1 "
+    "hashtree=PARTUUID=%U/PARTNROFF=1 hashstart=1536000 alg=sha1 "
+    "root_hexdigest=16b55bbea634fc3abf4c339da207cf050b1809d6 "
+    "salt=18a095c4e473b68558afefdf83438d482cf37894d312afce6991c8267ea233f6\" "
+    "noinitrd vt.global_cursor_default=0 kern_guid=%U ";
+
+// A real kernel command line from a parrot device, including the bootcache.
+static const char* kVerityAndBootcacheKernelCommandLine =
+    "console= loglevel=7 init=/sbin/init cros_secure oops=panic panic=-1 "
+    "root=/dev/dm-1 rootwait ro dm_verity.error_behavior=3 "
+    "dm_verity.max_bios=-1 dm_verity.dev_wait=1 "
+    "dm=\"2 vboot none ro 1,0 2545920 bootcache PARTUUID=%U/PARTNROFF=1 "
+    "2545920 d5d03fb5459b6a75f069378c1799ba313d8ea89a 512 20000 100000, vroot "
+    "none ro 1,0 2506752 verity payload=254:0 hashtree=254:0 hashstart=2506752 "
+    "alg=sha1 root_hexdigest=3deebbc697a30cc585cf85a3b4351dc772861321 "
+    "salt=6a13027cdf234c58a0b1f43e6a7428f41672cca89d5574c1f405649df65fb071\" "
+    "noinitrd vt.global_cursor_default=0 kern_guid=%U add_efi_memmap "
+    "boot=local noresume noswap i915.modeset=1 tpm_tis.force=1 "
+    "tpm_tis.interrupts=0 nmi_watchdog=panic,lapic "
+    "iTCO_vendor_support.vendorsupport=3";
+
+TEST(VerityUtilsTest, ParseVerityRootfsSizeWithInvalidValues) {
+  uint64_t rootfs_size = 0;
+  EXPECT_FALSE(ParseVerityRootfsSize("", &rootfs_size));
+
+  // Not a verity dm device.
+  EXPECT_FALSE(ParseVerityRootfsSize(
+      "dm=\"1 vroot none ro 1,0 1234 something\"", &rootfs_size));
+  EXPECT_FALSE(ParseVerityRootfsSize(
+      "ro verity hashattr=1234", &rootfs_size));
+
+  // The verity doesn't have the hashstart= attribute.
+  EXPECT_FALSE(ParseVerityRootfsSize(
+      "dm=\"1 vroot none ro 1,0 1234 verity payload=fake\"", &rootfs_size));
+}
+
+TEST(VerityUtilsTest, ParseVerityRootfsSizeWithValidValues) {
+  uint64_t rootfs_size = 0;
+  EXPECT_TRUE(ParseVerityRootfsSize(kVerityKernelCommandLine, &rootfs_size));
+  EXPECT_EQ(1536000 * 512, rootfs_size);
+  EXPECT_TRUE(ParseVerityRootfsSize(kVerityAndBootcacheKernelCommandLine,
+                                    &rootfs_size));
+  EXPECT_EQ(2506752 * 512, rootfs_size);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine.gyp b/update_engine.gyp
index 8dd5d2d..e8db1ed 100644
--- a/update_engine.gyp
+++ b/update_engine.gyp
@@ -273,6 +273,9 @@
             '<@(exported_deps)',
           ],
         },
+        'libraries': [
+          '-lvboot_host',
+        ],
       },
       'sources': [
         'payload_generator/cycle_breaker.cc',
@@ -288,6 +291,7 @@
         'payload_generator/payload_signer.cc',
         'payload_generator/tarjan.cc',
         'payload_generator/topological_sort.cc',
+        'payload_generator/verity_utils.cc',
       ],
     },
     # server-side delta generator.
@@ -381,6 +385,7 @@
             'payload_generator/payload_signer_unittest.cc',
             'payload_generator/tarjan_unittest.cc',
             'payload_generator/topological_sort_unittest.cc',
+            'payload_generator/verity_utils_unittest.cc',
             'payload_state_unittest.cc',
             'postinstall_runner_action_unittest.cc',
             'prefs_unittest.cc',