libavb: Don't pass androidboot.slot_suffix in generated kernel command-line.

It's not appropriate to do this since the boot loader's A/B stack will
likely do this. Also add new avb_strdupv() utility function so it's
easy to do this yourself and update UEFI example bootloader to use
this.

Bug: None
Test: New unit tests + all unit tests pass.
Test: Manually tested on UEFI based bootloader.
Change-Id: I9f9596b1f273330e80a38d857233167fefcce01b
diff --git a/examples/uefi/main.c b/examples/uefi/main.c
index 8d9c0ee..c5a0030 100644
--- a/examples/uefi/main.c
+++ b/examples/uefi/main.c
@@ -38,6 +38,7 @@
   UEFIAvbBootKernelResult boot_result;
   const char* requested_partitions[] = {"boot", NULL};
   bool unlocked = true;
+  char* additional_cmdline = NULL;
 
   InitializeLib(ImageHandle, SystemTable);
 
@@ -69,14 +70,24 @@
     case AVB_AB_FLOW_RESULT_OK_WITH_VERIFICATION_ERROR:
       avb_printv("slot_suffix: ", slot_data->ab_suffix, "\n", NULL);
       avb_printv("cmdline:     ", slot_data->cmdline, "\n", NULL);
-      /* Pass 'skip_initramfs' since we're not booting into recovery mode. */
+      /* Pass 'skip_initramfs' since we're not booting into recovery
+       * mode. Also pass the selected slot in androidboot.slot_suffix.
+       */
+      additional_cmdline = avb_strdupv("skip_initramfs ",
+                                       "androidboot.slot_suffix=",
+                                       slot_data->ab_suffix,
+                                       NULL);
+      if (additional_cmdline == NULL) {
+        avb_fatal("Error allocating additional_cmdline.\n");
+      }
       boot_result =
-          uefi_avb_boot_kernel(ImageHandle, slot_data, "skip_initramfs");
+          uefi_avb_boot_kernel(ImageHandle, slot_data, additional_cmdline);
       avb_fatalv("uefi_avb_boot_kernel() failed with error ",
                  uefi_avb_boot_kernel_result_to_string(boot_result),
                  "\n",
                  NULL);
       avb_slot_verify_data_free(slot_data);
+      avb_free(additional_cmdline);
       break;
     case AVB_AB_FLOW_RESULT_ERROR_OOM:
       avb_fatal("OOM error while doing A/B select flow.\n");
diff --git a/libavb/avb_slot_verify.c b/libavb/avb_slot_verify.c
index 296d831..f1d9de9 100644
--- a/libavb/avb_slot_verify.c
+++ b/libavb/avb_slot_verify.c
@@ -911,15 +911,6 @@
       slot_data->cmdline = new_cmdline;
     }
 
-    /* Add androidboot.slot_suffix, if applicable. */
-    if (avb_strlen(ab_suffix) > 0) {
-      if (!cmdline_append_option(
-              slot_data, "androidboot.slot_suffix", ab_suffix)) {
-        ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
-        goto fail;
-      }
-    }
-
     /* Set androidboot.avb.device_state to "locked" or "unlocked". */
     bool is_device_unlocked;
     io_ret = ops->read_is_device_unlocked(ops, &is_device_unlocked);
diff --git a/libavb/avb_slot_verify.h b/libavb/avb_slot_verify.h
index 93c12ba..a601bd3 100644
--- a/libavb/avb_slot_verify.h
+++ b/libavb/avb_slot_verify.h
@@ -138,10 +138,6 @@
  *   depending on the result of the result of AvbOps's
  *   read_is_unlocked() function.
  *
- *   androidboot.slot_suffix: If |ab_suffix| as passed into
- *   avb_slot_verify() is non-empty, this variable will be set to its
- *   value.
- *
  *   androidboot.vbmeta.{hash_alg, size, digest}: Will be set to
  *   the digest of all images in |vbmeta_images|.
  *
@@ -150,6 +146,9 @@
  *   will end up pointing to the vbmeta partition for the verified
  *   slot.
  *
+ * Note that androidboot.slot_suffix is not set in |cmdline| - you
+ * will have to do pass this command-line option yourself.
+ *
  * This struct may grow in the future without it being considered an
  * ABI break.
  */
diff --git a/libavb/avb_util.c b/libavb/avb_util.c
index 5a57b62..ba0f278 100644
--- a/libavb/avb_util.c
+++ b/libavb/avb_util.c
@@ -24,6 +24,8 @@
 
 #include "avb_util.h"
 
+#include <stdarg.h>
+
 uint32_t avb_be32toh(uint32_t in) {
   uint8_t* d = (uint8_t*)&in;
   uint32_t ret;
@@ -336,3 +338,46 @@
 out:
   return ret;
 }
+
+/* We only support a limited amount of strings in avb_strdupv(). */
+#define STRDUPV_MAX_NUM_STRINGS 32
+
+char* avb_strdupv(const char* str, ...) {
+  va_list ap;
+  const char* strings[STRDUPV_MAX_NUM_STRINGS];
+  size_t lengths[STRDUPV_MAX_NUM_STRINGS];
+  size_t num_strings, n, total_length;
+  char *ret = NULL, *dest;
+
+  num_strings = 0;
+  total_length = 0;
+  va_start(ap, str);
+  do {
+    strings[num_strings] = str;
+    lengths[num_strings] = avb_strlen(str);
+    total_length += lengths[num_strings];
+    num_strings++;
+    if (num_strings == STRDUPV_MAX_NUM_STRINGS) {
+      avb_fatal("Too many strings passed to avb_strdupv().\n");
+      break;
+    }
+    str = va_arg(ap, const char*);
+  } while (str != NULL);
+  va_end(ap);
+
+  ret = avb_malloc(total_length + 1);
+  if (ret == NULL) {
+    goto out;
+  }
+
+  dest = ret;
+  for (n = 0; n < num_strings; n++) {
+    avb_memcpy(dest, strings[n], lengths[n]);
+    dest += lengths[n];
+  }
+  *dest = '\0';
+  avb_assert(dest == ret + total_length);
+
+out:
+  return ret;
+}
diff --git a/libavb/avb_util.h b/libavb/avb_util.h
index 352829a..1342de7 100644
--- a/libavb/avb_util.h
+++ b/libavb/avb_util.h
@@ -200,6 +200,13 @@
 /* Duplicates a NUL-terminated string. Returns NULL on OOM. */
 char* avb_strdup(const char* str) AVB_ATTR_WARN_UNUSED_RESULT;
 
+/* Duplicates a NULL-terminated array of NUL-terminated strings by
+ * concatening them. The returned string will be
+ * NUL-terminated. Returns NULL on OOM.
+ */
+char* avb_strdupv(const char* str,
+                  ...) AVB_ATTR_WARN_UNUSED_RESULT AVB_ATTR_SENTINEL;
+
 /* Finds the first occurrence of |needle| in the string |haystack|
  * where both strings are NUL-terminated strings. The terminating NUL
  * bytes are not compared.
diff --git a/libavb_ab/avb_ab_flow.h b/libavb_ab/avb_ab_flow.h
index 6f6a3d2..bc18744 100644
--- a/libavb_ab/avb_ab_flow.h
+++ b/libavb_ab/avb_ab_flow.h
@@ -189,6 +189,10 @@
  * AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX for the slot in
  * question.
  *
+ * Note that androidboot.slot_suffix is not set in the |cmdline| field
+ * in |AvbSlotVerifyData| - you will have to do pass this command-line
+ * option yourself.
+ *
  * If a slot was selected and it verified then AVB_AB_FLOW_RESULT_OK
  * is returned.
  *
diff --git a/test/avb_slot_verify_unittest.cc b/test/avb_slot_verify_unittest.cc
index 51cfb23..5df5cfd 100644
--- a/test/avb_slot_verify_unittest.cc
+++ b/test/avb_slot_verify_unittest.cc
@@ -69,7 +69,7 @@
   EXPECT_NE(nullptr, slot_data);
   EXPECT_EQ(
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
-      "androidboot.slot_suffix=_a androidboot.vbmeta.device_state=locked "
+      "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=1152 "
       "androidboot.vbmeta.digest="
       "4161a7e655eabe16c3fe714de5d43736e7c0a190cf08d36c946d2509ce071e4d",
@@ -97,7 +97,7 @@
   EXPECT_NE(nullptr, slot_data);
   EXPECT_EQ(
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
-      "androidboot.slot_suffix=_a androidboot.vbmeta.device_state=locked "
+      "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha512 androidboot.vbmeta.size=1152 "
       "androidboot.vbmeta.digest="
       "cb913d2f1a884f4e04c1db5bb181f3133fd16ac02fb367a20ef0776c0b07b3656ad1f081"
@@ -128,7 +128,7 @@
   EXPECT_NE(nullptr, slot_data);
   EXPECT_EQ(
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
-      "androidboot.slot_suffix=_a androidboot.vbmeta.device_state=unlocked "
+      "androidboot.vbmeta.device_state=unlocked "
       "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=1152 "
       "androidboot.vbmeta.digest="
       "4161a7e655eabe16c3fe714de5d43736e7c0a190cf08d36c946d2509ce071e4d",
@@ -428,7 +428,7 @@
       "cmdline in vbmeta 1234-fake-guid-for:boot_a cmdline in hash footer "
       "1234-fake-guid-for:system_a "
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
-      "androidboot.slot_suffix=_a androidboot.vbmeta.device_state=locked "
+      "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=1472 "
       "androidboot.vbmeta.digest="
       "34cdb59b955aa35d4da97701f304fabf7392eecca8c50ff1a0b7b6e1c9aaa1b8",
@@ -658,7 +658,7 @@
   EXPECT_EQ(
       "cmdline2 in hash footer cmdline2 in vbmeta "
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
-      "androidboot.slot_suffix=_a androidboot.vbmeta.device_state=locked "
+      "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=4416 "
       "androidboot.vbmeta.digest="
       "4a45faa9adfeb94e9154fe682c11fef1a1a3d829b67cbf1a12ac7f0aa4f8e2e4",
@@ -968,8 +968,7 @@
   }
 
   // This should match the two cmdlines with a space (U+0020) between
-  // them - note that androidboot.slot_suffix is not set since we
-  // don't have any slots in this setup.
+  // them.
   EXPECT_EQ(
       "cmdline2 in hash footer cmdline2 in vbmeta "
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta "
@@ -1114,7 +1113,7 @@
   EXPECT_NE(nullptr, slot_data);
   EXPECT_EQ(
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
-      "androidboot.slot_suffix=_a androidboot.vbmeta.device_state=locked "
+      "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=2688 "
       "androidboot.vbmeta.digest="
       "5edcaa54f40382ee6a2fc3b86cdf383348b35ed07955e83ea32d84b69a97eaa0",
@@ -1214,7 +1213,7 @@
         "restart_on_corruption ignore_zero_blocks\" root=0xfd00 "
         "should_be_in_both=1 "
         "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
-        "androidboot.slot_suffix=_a androidboot.vbmeta.device_state=locked "
+        "androidboot.vbmeta.device_state=locked "
         "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=1472 "
         "androidboot.vbmeta.digest="
         "32572f7a89fb44cc76e9e3adbc0cb5272d0604578b2179729aa52d2bba4061c3",
@@ -1223,7 +1222,7 @@
     EXPECT_EQ(
         "root=PARTUUID=1234-fake-guid-for:system_a should_be_in_both=1 "
         "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
-        "androidboot.slot_suffix=_a androidboot.vbmeta.device_state=locked "
+        "androidboot.vbmeta.device_state=locked "
         "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=1472 "
         "androidboot.vbmeta.digest="
         "bb630c27d09e10117092d4444ea57a99bf44101c46cf5424cfb86928a0873249",
diff --git a/test/avb_util_unittest.cc b/test/avb_util_unittest.cc
index 0c8c375..4ee155d 100644
--- a/test/avb_util_unittest.cc
+++ b/test/avb_util_unittest.cc
@@ -470,6 +470,13 @@
             std::string(avb_replace("$(FOO)$(FOO)", "$(FOO)", "LONGSTRING")));
 }
 
+TEST(UtilTest, StrDupV) {
+  // We don't care about leaking avb_strdupv() result.
+  EXPECT_EQ("xyz", std::string(avb_strdupv("x", "y", "z", NULL)));
+  EXPECT_EQ("HelloWorld XYZ",
+            std::string(avb_strdupv("Hello", "World", " XYZ", NULL)));
+}
+
 TEST(UtilTest, Crc32) {
   /* Compare with output of crc32(1):
    *