xref: /openbmc/u-boot/lib/libavb/avb_cmdline.c (revision 28b538b6)
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * SPDX-License-Identifier:	MIT
5  */
6 
7 #include "avb_cmdline.h"
8 #include "avb_sha.h"
9 #include "avb_util.h"
10 #include "avb_version.h"
11 
12 #define NUM_GUIDS 3
13 
14 /* Substitutes all variables (e.g. $(ANDROID_SYSTEM_PARTUUID)) with
15  * values. Returns NULL on OOM, otherwise the cmdline with values
16  * replaced.
17  */
18 char* avb_sub_cmdline(AvbOps* ops,
19                       const char* cmdline,
20                       const char* ab_suffix,
21                       bool using_boot_for_vbmeta,
22                       const AvbCmdlineSubstList* additional_substitutions) {
23   const char* part_name_str[NUM_GUIDS] = {"system", "boot", "vbmeta"};
24   const char* replace_str[NUM_GUIDS] = {"$(ANDROID_SYSTEM_PARTUUID)",
25                                         "$(ANDROID_BOOT_PARTUUID)",
26                                         "$(ANDROID_VBMETA_PARTUUID)"};
27   char* ret = NULL;
28   AvbIOResult io_ret;
29   size_t n;
30 
31   /* Special-case for when the top-level vbmeta struct is in the boot
32    * partition.
33    */
34   if (using_boot_for_vbmeta) {
35     part_name_str[2] = "boot";
36   }
37 
38   /* Replace unique partition GUIDs */
39   for (n = 0; n < NUM_GUIDS; n++) {
40     char part_name[AVB_PART_NAME_MAX_SIZE];
41     char guid_buf[37];
42 
43     if (!avb_str_concat(part_name,
44                         sizeof part_name,
45                         part_name_str[n],
46                         avb_strlen(part_name_str[n]),
47                         ab_suffix,
48                         avb_strlen(ab_suffix))) {
49       avb_error("Partition name and suffix does not fit.\n");
50       goto fail;
51     }
52 
53     io_ret = ops->get_unique_guid_for_partition(
54         ops, part_name, guid_buf, sizeof guid_buf);
55     if (io_ret == AVB_IO_RESULT_ERROR_OOM) {
56       goto fail;
57     } else if (io_ret != AVB_IO_RESULT_OK) {
58       avb_error("Error getting unique GUID for partition.\n");
59       goto fail;
60     }
61 
62     if (ret == NULL) {
63       ret = avb_replace(cmdline, replace_str[n], guid_buf);
64     } else {
65       char* new_ret = avb_replace(ret, replace_str[n], guid_buf);
66       avb_free(ret);
67       ret = new_ret;
68     }
69     if (ret == NULL) {
70       goto fail;
71     }
72   }
73 
74   avb_assert(ret != NULL);
75 
76   /* Replace any additional substitutions. */
77   if (additional_substitutions != NULL) {
78     for (n = 0; n < additional_substitutions->size; ++n) {
79       char* new_ret = avb_replace(ret,
80                                   additional_substitutions->tokens[n],
81                                   additional_substitutions->values[n]);
82       avb_free(ret);
83       ret = new_ret;
84       if (ret == NULL) {
85         goto fail;
86       }
87     }
88   }
89 
90   return ret;
91 
92 fail:
93   if (ret != NULL) {
94     avb_free(ret);
95   }
96   return NULL;
97 }
98 
99 static int cmdline_append_option(AvbSlotVerifyData* slot_data,
100                                  const char* key,
101                                  const char* value) {
102   size_t offset, key_len, value_len;
103   char* new_cmdline;
104 
105   key_len = avb_strlen(key);
106   value_len = avb_strlen(value);
107 
108   offset = 0;
109   if (slot_data->cmdline != NULL) {
110     offset = avb_strlen(slot_data->cmdline);
111     if (offset > 0) {
112       offset += 1;
113     }
114   }
115 
116   new_cmdline = avb_calloc(offset + key_len + value_len + 2);
117   if (new_cmdline == NULL) {
118     return 0;
119   }
120   if (offset > 0) {
121     avb_memcpy(new_cmdline, slot_data->cmdline, offset - 1);
122     new_cmdline[offset - 1] = ' ';
123   }
124   avb_memcpy(new_cmdline + offset, key, key_len);
125   new_cmdline[offset + key_len] = '=';
126   avb_memcpy(new_cmdline + offset + key_len + 1, value, value_len);
127   if (slot_data->cmdline != NULL) {
128     avb_free(slot_data->cmdline);
129   }
130   slot_data->cmdline = new_cmdline;
131 
132   return 1;
133 }
134 
135 #define AVB_MAX_DIGITS_UINT64 32
136 
137 /* Writes |value| to |digits| in base 10 followed by a NUL byte.
138  * Returns number of characters written excluding the NUL byte.
139  */
140 static size_t uint64_to_base10(uint64_t value,
141                                char digits[AVB_MAX_DIGITS_UINT64]) {
142   char rev_digits[AVB_MAX_DIGITS_UINT64];
143   size_t n, num_digits;
144 
145   for (num_digits = 0; num_digits < AVB_MAX_DIGITS_UINT64 - 1;) {
146     rev_digits[num_digits++] = avb_div_by_10(&value) + '0';
147     if (value == 0) {
148       break;
149     }
150   }
151 
152   for (n = 0; n < num_digits; n++) {
153     digits[n] = rev_digits[num_digits - 1 - n];
154   }
155   digits[n] = '\0';
156   return n;
157 }
158 
159 static int cmdline_append_version(AvbSlotVerifyData* slot_data,
160                                   const char* key,
161                                   uint64_t major_version,
162                                   uint64_t minor_version) {
163   char major_digits[AVB_MAX_DIGITS_UINT64];
164   char minor_digits[AVB_MAX_DIGITS_UINT64];
165   char combined[AVB_MAX_DIGITS_UINT64 * 2 + 1];
166   size_t num_major_digits, num_minor_digits;
167 
168   num_major_digits = uint64_to_base10(major_version, major_digits);
169   num_minor_digits = uint64_to_base10(minor_version, minor_digits);
170   avb_memcpy(combined, major_digits, num_major_digits);
171   combined[num_major_digits] = '.';
172   avb_memcpy(combined + num_major_digits + 1, minor_digits, num_minor_digits);
173   combined[num_major_digits + 1 + num_minor_digits] = '\0';
174 
175   return cmdline_append_option(slot_data, key, combined);
176 }
177 
178 static int cmdline_append_uint64_base10(AvbSlotVerifyData* slot_data,
179                                         const char* key,
180                                         uint64_t value) {
181   char digits[AVB_MAX_DIGITS_UINT64];
182   uint64_to_base10(value, digits);
183   return cmdline_append_option(slot_data, key, digits);
184 }
185 
186 static int cmdline_append_hex(AvbSlotVerifyData* slot_data,
187                               const char* key,
188                               const uint8_t* data,
189                               size_t data_len) {
190   int ret;
191   char* hex_data = avb_bin2hex(data, data_len);
192   if (hex_data == NULL) {
193     return 0;
194   }
195   ret = cmdline_append_option(slot_data, key, hex_data);
196   avb_free(hex_data);
197   return ret;
198 }
199 
200 AvbSlotVerifyResult avb_append_options(
201     AvbOps* ops,
202     AvbSlotVerifyData* slot_data,
203     AvbVBMetaImageHeader* toplevel_vbmeta,
204     AvbAlgorithmType algorithm_type,
205     AvbHashtreeErrorMode hashtree_error_mode) {
206   AvbSlotVerifyResult ret;
207   const char* verity_mode;
208   bool is_device_unlocked;
209   AvbIOResult io_ret;
210 
211   /* Add androidboot.vbmeta.device option. */
212   if (!cmdline_append_option(slot_data,
213                              "androidboot.vbmeta.device",
214                              "PARTUUID=$(ANDROID_VBMETA_PARTUUID)")) {
215     ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
216     goto out;
217   }
218 
219   /* Add androidboot.vbmeta.avb_version option. */
220   if (!cmdline_append_version(slot_data,
221                               "androidboot.vbmeta.avb_version",
222                               AVB_VERSION_MAJOR,
223                               AVB_VERSION_MINOR)) {
224     ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
225     goto out;
226   }
227 
228   /* Set androidboot.avb.device_state to "locked" or "unlocked". */
229   io_ret = ops->read_is_device_unlocked(ops, &is_device_unlocked);
230   if (io_ret == AVB_IO_RESULT_ERROR_OOM) {
231     ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
232     goto out;
233   } else if (io_ret != AVB_IO_RESULT_OK) {
234     avb_error("Error getting device state.\n");
235     ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO;
236     goto out;
237   }
238   if (!cmdline_append_option(slot_data,
239                              "androidboot.vbmeta.device_state",
240                              is_device_unlocked ? "unlocked" : "locked")) {
241     ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
242     goto out;
243   }
244 
245   /* Set androidboot.vbmeta.{hash_alg, size, digest} - use same hash
246    * function as is used to sign vbmeta.
247    */
248   switch (algorithm_type) {
249     /* Explicit fallthrough. */
250     case AVB_ALGORITHM_TYPE_NONE:
251     case AVB_ALGORITHM_TYPE_SHA256_RSA2048:
252     case AVB_ALGORITHM_TYPE_SHA256_RSA4096:
253     case AVB_ALGORITHM_TYPE_SHA256_RSA8192: {
254       size_t n, total_size = 0;
255       uint8_t vbmeta_digest[AVB_SHA256_DIGEST_SIZE];
256       avb_slot_verify_data_calculate_vbmeta_digest(
257           slot_data, AVB_DIGEST_TYPE_SHA256, vbmeta_digest);
258       for (n = 0; n < slot_data->num_vbmeta_images; n++) {
259         total_size += slot_data->vbmeta_images[n].vbmeta_size;
260       }
261       if (!cmdline_append_option(
262               slot_data, "androidboot.vbmeta.hash_alg", "sha256") ||
263           !cmdline_append_uint64_base10(
264               slot_data, "androidboot.vbmeta.size", total_size) ||
265           !cmdline_append_hex(slot_data,
266                               "androidboot.vbmeta.digest",
267                               vbmeta_digest,
268                               AVB_SHA256_DIGEST_SIZE)) {
269         ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
270         goto out;
271       }
272     } break;
273     /* Explicit fallthrough. */
274     case AVB_ALGORITHM_TYPE_SHA512_RSA2048:
275     case AVB_ALGORITHM_TYPE_SHA512_RSA4096:
276     case AVB_ALGORITHM_TYPE_SHA512_RSA8192: {
277       size_t n, total_size = 0;
278       uint8_t vbmeta_digest[AVB_SHA512_DIGEST_SIZE];
279       avb_slot_verify_data_calculate_vbmeta_digest(
280           slot_data, AVB_DIGEST_TYPE_SHA512, vbmeta_digest);
281       for (n = 0; n < slot_data->num_vbmeta_images; n++) {
282         total_size += slot_data->vbmeta_images[n].vbmeta_size;
283       }
284       if (!cmdline_append_option(
285               slot_data, "androidboot.vbmeta.hash_alg", "sha512") ||
286           !cmdline_append_uint64_base10(
287               slot_data, "androidboot.vbmeta.size", total_size) ||
288           !cmdline_append_hex(slot_data,
289                               "androidboot.vbmeta.digest",
290                               vbmeta_digest,
291                               AVB_SHA512_DIGEST_SIZE)) {
292         ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
293         goto out;
294       }
295     } break;
296     case _AVB_ALGORITHM_NUM_TYPES:
297       avb_assert_not_reached();
298       break;
299   }
300 
301   /* Set androidboot.veritymode and androidboot.vbmeta.invalidate_on_error */
302   if (toplevel_vbmeta->flags & AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED) {
303     verity_mode = "disabled";
304   } else {
305     const char* dm_verity_mode;
306     char* new_ret;
307 
308     switch (hashtree_error_mode) {
309       case AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE:
310         if (!cmdline_append_option(
311                 slot_data, "androidboot.vbmeta.invalidate_on_error", "yes")) {
312           ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
313           goto out;
314         }
315         verity_mode = "enforcing";
316         dm_verity_mode = "restart_on_corruption";
317         break;
318       case AVB_HASHTREE_ERROR_MODE_RESTART:
319         verity_mode = "enforcing";
320         dm_verity_mode = "restart_on_corruption";
321         break;
322       case AVB_HASHTREE_ERROR_MODE_EIO:
323         verity_mode = "eio";
324         /* For now there's no option to specify the EIO mode. So
325          * just use 'ignore_zero_blocks' since that's already set
326          * and dm-verity-target.c supports specifying this multiple
327          * times.
328          */
329         dm_verity_mode = "ignore_zero_blocks";
330         break;
331       case AVB_HASHTREE_ERROR_MODE_LOGGING:
332         verity_mode = "logging";
333         dm_verity_mode = "ignore_corruption";
334         break;
335     }
336     new_ret = avb_replace(
337         slot_data->cmdline, "$(ANDROID_VERITY_MODE)", dm_verity_mode);
338     avb_free(slot_data->cmdline);
339     slot_data->cmdline = new_ret;
340     if (slot_data->cmdline == NULL) {
341       ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
342       goto out;
343     }
344   }
345   if (!cmdline_append_option(
346           slot_data, "androidboot.veritymode", verity_mode)) {
347     ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
348     goto out;
349   }
350 
351   ret = AVB_SLOT_VERIFY_RESULT_OK;
352 
353 out:
354 
355   return ret;
356 }
357 
358 AvbCmdlineSubstList* avb_new_cmdline_subst_list() {
359   return (AvbCmdlineSubstList*)avb_calloc(sizeof(AvbCmdlineSubstList));
360 }
361 
362 void avb_free_cmdline_subst_list(AvbCmdlineSubstList* cmdline_subst) {
363   size_t i;
364   for (i = 0; i < cmdline_subst->size; ++i) {
365     avb_free(cmdline_subst->tokens[i]);
366     avb_free(cmdline_subst->values[i]);
367   }
368   cmdline_subst->size = 0;
369   avb_free(cmdline_subst);
370 }
371 
372 AvbSlotVerifyResult avb_add_root_digest_substitution(
373     const char* part_name,
374     const uint8_t* digest,
375     size_t digest_size,
376     AvbCmdlineSubstList* out_cmdline_subst) {
377   const char* kDigestSubPrefix = "$(AVB_";
378   const char* kDigestSubSuffix = "_ROOT_DIGEST)";
379   size_t part_name_len = avb_strlen(part_name);
380   size_t list_index = out_cmdline_subst->size;
381 
382   avb_assert(part_name_len < AVB_PART_NAME_MAX_SIZE);
383   avb_assert(digest_size <= AVB_SHA512_DIGEST_SIZE);
384   if (part_name_len >= AVB_PART_NAME_MAX_SIZE ||
385       digest_size > AVB_SHA512_DIGEST_SIZE) {
386     return AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
387   }
388 
389   if (out_cmdline_subst->size >= AVB_MAX_NUM_CMDLINE_SUBST) {
390     /* The list is full. Currently dynamic growth of this list is not supported.
391      */
392     return AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
393   }
394 
395   /* Construct the token to replace in the command line based on the partition
396    * name. For partition 'foo', this will be '$(AVB_FOO_ROOT_DIGEST)'.
397    */
398   out_cmdline_subst->tokens[list_index] =
399       avb_strdupv(kDigestSubPrefix, part_name, kDigestSubSuffix, NULL);
400   if (out_cmdline_subst->tokens[list_index] == NULL) {
401     goto fail;
402   }
403   avb_uppercase(out_cmdline_subst->tokens[list_index]);
404 
405   /* The digest value is hex encoded when inserted in the command line. */
406   out_cmdline_subst->values[list_index] = avb_bin2hex(digest, digest_size);
407   if (out_cmdline_subst->values[list_index] == NULL) {
408     goto fail;
409   }
410 
411   out_cmdline_subst->size++;
412   return AVB_SLOT_VERIFY_RESULT_OK;
413 
414 fail:
415   if (out_cmdline_subst->tokens[list_index]) {
416     avb_free(out_cmdline_subst->tokens[list_index]);
417   }
418   if (out_cmdline_subst->values[list_index]) {
419     avb_free(out_cmdline_subst->values[list_index]);
420   }
421   return AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
422 }
423