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/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