blob: dd859d3467ea74447ce7fb249f0d3a77138861fa [file] [log] [blame]
Tom Rini897a1d92018-06-19 11:21:44 -04001// SPDX-License-Identifier: MIT
Igor Opaniukd8f9d2a2018-06-03 21:56:36 +03002/*
3 * Copyright (C) 2016 The Android Open Source Project
Igor Opaniukd8f9d2a2018-06-03 21:56:36 +03004 */
5
6#include "avb_cmdline.h"
7#include "avb_sha.h"
8#include "avb_util.h"
9#include "avb_version.h"
Simon Glass336d4612020-02-03 07:36:16 -070010#include <malloc.h>
Igor Opaniukd8f9d2a2018-06-03 21:56:36 +030011
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 */
18char* 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
Sam Protsenko4d579a42019-08-15 23:04:02 +030043 /* Don't attempt to query the partition guid unless its search string is
44 * present in the command line. Note: the original cmdline is used here,
45 * not the replaced one. See b/116010959.
46 */
47 if (avb_strstr(cmdline, replace_str[n]) == NULL) {
48 continue;
49 }
50
Igor Opaniukd8f9d2a2018-06-03 21:56:36 +030051 if (!avb_str_concat(part_name,
52 sizeof part_name,
53 part_name_str[n],
54 avb_strlen(part_name_str[n]),
55 ab_suffix,
56 avb_strlen(ab_suffix))) {
57 avb_error("Partition name and suffix does not fit.\n");
58 goto fail;
59 }
60
61 io_ret = ops->get_unique_guid_for_partition(
62 ops, part_name, guid_buf, sizeof guid_buf);
63 if (io_ret == AVB_IO_RESULT_ERROR_OOM) {
64 goto fail;
65 } else if (io_ret != AVB_IO_RESULT_OK) {
66 avb_error("Error getting unique GUID for partition.\n");
67 goto fail;
68 }
69
70 if (ret == NULL) {
71 ret = avb_replace(cmdline, replace_str[n], guid_buf);
72 } else {
73 char* new_ret = avb_replace(ret, replace_str[n], guid_buf);
74 avb_free(ret);
75 ret = new_ret;
76 }
77 if (ret == NULL) {
78 goto fail;
79 }
80 }
81
Sam Protsenko4d579a42019-08-15 23:04:02 +030082 /* It's possible there is no _PARTUUID for replacement above.
83 * Duplicate cmdline to ret for additional substitutions below.
84 */
85 if (ret == NULL) {
86 ret = avb_strdup(cmdline);
87 if (ret == NULL) {
88 goto fail;
89 }
90 }
Igor Opaniukd8f9d2a2018-06-03 21:56:36 +030091
92 /* Replace any additional substitutions. */
93 if (additional_substitutions != NULL) {
94 for (n = 0; n < additional_substitutions->size; ++n) {
95 char* new_ret = avb_replace(ret,
96 additional_substitutions->tokens[n],
97 additional_substitutions->values[n]);
98 avb_free(ret);
99 ret = new_ret;
100 if (ret == NULL) {
101 goto fail;
102 }
103 }
104 }
105
106 return ret;
107
108fail:
109 if (ret != NULL) {
110 avb_free(ret);
111 }
112 return NULL;
113}
114
115static int cmdline_append_option(AvbSlotVerifyData* slot_data,
116 const char* key,
117 const char* value) {
118 size_t offset, key_len, value_len;
119 char* new_cmdline;
120
121 key_len = avb_strlen(key);
122 value_len = avb_strlen(value);
123
124 offset = 0;
125 if (slot_data->cmdline != NULL) {
126 offset = avb_strlen(slot_data->cmdline);
127 if (offset > 0) {
128 offset += 1;
129 }
130 }
131
132 new_cmdline = avb_calloc(offset + key_len + value_len + 2);
133 if (new_cmdline == NULL) {
134 return 0;
135 }
136 if (offset > 0) {
137 avb_memcpy(new_cmdline, slot_data->cmdline, offset - 1);
138 new_cmdline[offset - 1] = ' ';
139 }
140 avb_memcpy(new_cmdline + offset, key, key_len);
141 new_cmdline[offset + key_len] = '=';
142 avb_memcpy(new_cmdline + offset + key_len + 1, value, value_len);
143 if (slot_data->cmdline != NULL) {
144 avb_free(slot_data->cmdline);
145 }
146 slot_data->cmdline = new_cmdline;
147
148 return 1;
149}
150
151#define AVB_MAX_DIGITS_UINT64 32
152
153/* Writes |value| to |digits| in base 10 followed by a NUL byte.
154 * Returns number of characters written excluding the NUL byte.
155 */
156static size_t uint64_to_base10(uint64_t value,
157 char digits[AVB_MAX_DIGITS_UINT64]) {
158 char rev_digits[AVB_MAX_DIGITS_UINT64];
159 size_t n, num_digits;
160
161 for (num_digits = 0; num_digits < AVB_MAX_DIGITS_UINT64 - 1;) {
162 rev_digits[num_digits++] = avb_div_by_10(&value) + '0';
163 if (value == 0) {
164 break;
165 }
166 }
167
168 for (n = 0; n < num_digits; n++) {
169 digits[n] = rev_digits[num_digits - 1 - n];
170 }
171 digits[n] = '\0';
172 return n;
173}
174
175static int cmdline_append_version(AvbSlotVerifyData* slot_data,
176 const char* key,
177 uint64_t major_version,
178 uint64_t minor_version) {
179 char major_digits[AVB_MAX_DIGITS_UINT64];
180 char minor_digits[AVB_MAX_DIGITS_UINT64];
181 char combined[AVB_MAX_DIGITS_UINT64 * 2 + 1];
182 size_t num_major_digits, num_minor_digits;
183
184 num_major_digits = uint64_to_base10(major_version, major_digits);
185 num_minor_digits = uint64_to_base10(minor_version, minor_digits);
186 avb_memcpy(combined, major_digits, num_major_digits);
187 combined[num_major_digits] = '.';
188 avb_memcpy(combined + num_major_digits + 1, minor_digits, num_minor_digits);
189 combined[num_major_digits + 1 + num_minor_digits] = '\0';
190
191 return cmdline_append_option(slot_data, key, combined);
192}
193
194static int cmdline_append_uint64_base10(AvbSlotVerifyData* slot_data,
195 const char* key,
196 uint64_t value) {
197 char digits[AVB_MAX_DIGITS_UINT64];
198 uint64_to_base10(value, digits);
199 return cmdline_append_option(slot_data, key, digits);
200}
201
202static int cmdline_append_hex(AvbSlotVerifyData* slot_data,
203 const char* key,
204 const uint8_t* data,
205 size_t data_len) {
206 int ret;
207 char* hex_data = avb_bin2hex(data, data_len);
208 if (hex_data == NULL) {
209 return 0;
210 }
211 ret = cmdline_append_option(slot_data, key, hex_data);
212 avb_free(hex_data);
213 return ret;
214}
215
216AvbSlotVerifyResult avb_append_options(
217 AvbOps* ops,
Sam Protsenko4d579a42019-08-15 23:04:02 +0300218 AvbSlotVerifyFlags flags,
Igor Opaniukd8f9d2a2018-06-03 21:56:36 +0300219 AvbSlotVerifyData* slot_data,
220 AvbVBMetaImageHeader* toplevel_vbmeta,
221 AvbAlgorithmType algorithm_type,
Sam Protsenko4d579a42019-08-15 23:04:02 +0300222 AvbHashtreeErrorMode hashtree_error_mode,
223 AvbHashtreeErrorMode resolved_hashtree_error_mode) {
Igor Opaniukd8f9d2a2018-06-03 21:56:36 +0300224 AvbSlotVerifyResult ret;
225 const char* verity_mode;
226 bool is_device_unlocked;
227 AvbIOResult io_ret;
228
Sam Protsenko4d579a42019-08-15 23:04:02 +0300229 /* Add androidboot.vbmeta.device option... except if not using a vbmeta
230 * partition since it doesn't make sense in that case.
231 */
232 if (!(flags & AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION)) {
233 if (!cmdline_append_option(slot_data,
234 "androidboot.vbmeta.device",
235 "PARTUUID=$(ANDROID_VBMETA_PARTUUID)")) {
236 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
237 goto out;
238 }
Igor Opaniukd8f9d2a2018-06-03 21:56:36 +0300239 }
240
241 /* Add androidboot.vbmeta.avb_version option. */
242 if (!cmdline_append_version(slot_data,
243 "androidboot.vbmeta.avb_version",
244 AVB_VERSION_MAJOR,
245 AVB_VERSION_MINOR)) {
246 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
247 goto out;
248 }
249
250 /* Set androidboot.avb.device_state to "locked" or "unlocked". */
251 io_ret = ops->read_is_device_unlocked(ops, &is_device_unlocked);
252 if (io_ret == AVB_IO_RESULT_ERROR_OOM) {
253 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
254 goto out;
255 } else if (io_ret != AVB_IO_RESULT_OK) {
256 avb_error("Error getting device state.\n");
257 ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO;
258 goto out;
259 }
260 if (!cmdline_append_option(slot_data,
261 "androidboot.vbmeta.device_state",
262 is_device_unlocked ? "unlocked" : "locked")) {
263 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
264 goto out;
265 }
266
267 /* Set androidboot.vbmeta.{hash_alg, size, digest} - use same hash
268 * function as is used to sign vbmeta.
269 */
270 switch (algorithm_type) {
271 /* Explicit fallthrough. */
272 case AVB_ALGORITHM_TYPE_NONE:
273 case AVB_ALGORITHM_TYPE_SHA256_RSA2048:
274 case AVB_ALGORITHM_TYPE_SHA256_RSA4096:
275 case AVB_ALGORITHM_TYPE_SHA256_RSA8192: {
276 size_t n, total_size = 0;
277 uint8_t vbmeta_digest[AVB_SHA256_DIGEST_SIZE];
278 avb_slot_verify_data_calculate_vbmeta_digest(
279 slot_data, AVB_DIGEST_TYPE_SHA256, vbmeta_digest);
280 for (n = 0; n < slot_data->num_vbmeta_images; n++) {
281 total_size += slot_data->vbmeta_images[n].vbmeta_size;
282 }
283 if (!cmdline_append_option(
284 slot_data, "androidboot.vbmeta.hash_alg", "sha256") ||
285 !cmdline_append_uint64_base10(
286 slot_data, "androidboot.vbmeta.size", total_size) ||
287 !cmdline_append_hex(slot_data,
288 "androidboot.vbmeta.digest",
289 vbmeta_digest,
290 AVB_SHA256_DIGEST_SIZE)) {
291 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
292 goto out;
293 }
294 } break;
295 /* Explicit fallthrough. */
296 case AVB_ALGORITHM_TYPE_SHA512_RSA2048:
297 case AVB_ALGORITHM_TYPE_SHA512_RSA4096:
298 case AVB_ALGORITHM_TYPE_SHA512_RSA8192: {
299 size_t n, total_size = 0;
300 uint8_t vbmeta_digest[AVB_SHA512_DIGEST_SIZE];
301 avb_slot_verify_data_calculate_vbmeta_digest(
302 slot_data, AVB_DIGEST_TYPE_SHA512, vbmeta_digest);
303 for (n = 0; n < slot_data->num_vbmeta_images; n++) {
304 total_size += slot_data->vbmeta_images[n].vbmeta_size;
305 }
306 if (!cmdline_append_option(
307 slot_data, "androidboot.vbmeta.hash_alg", "sha512") ||
308 !cmdline_append_uint64_base10(
309 slot_data, "androidboot.vbmeta.size", total_size) ||
310 !cmdline_append_hex(slot_data,
311 "androidboot.vbmeta.digest",
312 vbmeta_digest,
313 AVB_SHA512_DIGEST_SIZE)) {
314 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
315 goto out;
316 }
317 } break;
318 case _AVB_ALGORITHM_NUM_TYPES:
319 avb_assert_not_reached();
320 break;
321 }
322
323 /* Set androidboot.veritymode and androidboot.vbmeta.invalidate_on_error */
324 if (toplevel_vbmeta->flags & AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED) {
325 verity_mode = "disabled";
326 } else {
327 const char* dm_verity_mode;
328 char* new_ret;
329
Sam Protsenko4d579a42019-08-15 23:04:02 +0300330 switch (resolved_hashtree_error_mode) {
Igor Opaniukd8f9d2a2018-06-03 21:56:36 +0300331 case AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE:
332 if (!cmdline_append_option(
333 slot_data, "androidboot.vbmeta.invalidate_on_error", "yes")) {
334 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
335 goto out;
336 }
337 verity_mode = "enforcing";
338 dm_verity_mode = "restart_on_corruption";
339 break;
340 case AVB_HASHTREE_ERROR_MODE_RESTART:
341 verity_mode = "enforcing";
342 dm_verity_mode = "restart_on_corruption";
343 break;
344 case AVB_HASHTREE_ERROR_MODE_EIO:
345 verity_mode = "eio";
346 /* For now there's no option to specify the EIO mode. So
347 * just use 'ignore_zero_blocks' since that's already set
348 * and dm-verity-target.c supports specifying this multiple
349 * times.
350 */
351 dm_verity_mode = "ignore_zero_blocks";
352 break;
353 case AVB_HASHTREE_ERROR_MODE_LOGGING:
354 verity_mode = "logging";
355 dm_verity_mode = "ignore_corruption";
356 break;
Sam Protsenko4d579a42019-08-15 23:04:02 +0300357 case AVB_HASHTREE_ERROR_MODE_MANAGED_RESTART_AND_EIO:
358 // Should never get here because MANAGED_RESTART_AND_EIO is
359 // remapped by avb_manage_hashtree_error_mode().
360 avb_assert_not_reached();
Sam Protsenko8f684b52019-08-15 23:04:03 +0300361 ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT;
362 goto out;
Ievgen Maliarenkoecc6f6b2018-08-14 02:43:03 +0200363 default:
364 ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT;
365 goto out;
Igor Opaniukd8f9d2a2018-06-03 21:56:36 +0300366 }
367 new_ret = avb_replace(
368 slot_data->cmdline, "$(ANDROID_VERITY_MODE)", dm_verity_mode);
369 avb_free(slot_data->cmdline);
370 slot_data->cmdline = new_ret;
371 if (slot_data->cmdline == NULL) {
372 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
373 goto out;
374 }
375 }
376 if (!cmdline_append_option(
377 slot_data, "androidboot.veritymode", verity_mode)) {
378 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
379 goto out;
380 }
Sam Protsenko4d579a42019-08-15 23:04:02 +0300381 if (hashtree_error_mode == AVB_HASHTREE_ERROR_MODE_MANAGED_RESTART_AND_EIO) {
382 if (!cmdline_append_option(
383 slot_data, "androidboot.veritymode.managed", "yes")) {
384 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
385 goto out;
386 }
387 }
Igor Opaniukd8f9d2a2018-06-03 21:56:36 +0300388
389 ret = AVB_SLOT_VERIFY_RESULT_OK;
390
391out:
392
393 return ret;
394}
395
396AvbCmdlineSubstList* avb_new_cmdline_subst_list() {
397 return (AvbCmdlineSubstList*)avb_calloc(sizeof(AvbCmdlineSubstList));
398}
399
400void avb_free_cmdline_subst_list(AvbCmdlineSubstList* cmdline_subst) {
401 size_t i;
402 for (i = 0; i < cmdline_subst->size; ++i) {
403 avb_free(cmdline_subst->tokens[i]);
404 avb_free(cmdline_subst->values[i]);
405 }
406 cmdline_subst->size = 0;
407 avb_free(cmdline_subst);
408}
409
410AvbSlotVerifyResult avb_add_root_digest_substitution(
411 const char* part_name,
412 const uint8_t* digest,
413 size_t digest_size,
414 AvbCmdlineSubstList* out_cmdline_subst) {
415 const char* kDigestSubPrefix = "$(AVB_";
416 const char* kDigestSubSuffix = "_ROOT_DIGEST)";
417 size_t part_name_len = avb_strlen(part_name);
418 size_t list_index = out_cmdline_subst->size;
419
420 avb_assert(part_name_len < AVB_PART_NAME_MAX_SIZE);
421 avb_assert(digest_size <= AVB_SHA512_DIGEST_SIZE);
422 if (part_name_len >= AVB_PART_NAME_MAX_SIZE ||
423 digest_size > AVB_SHA512_DIGEST_SIZE) {
424 return AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
425 }
426
427 if (out_cmdline_subst->size >= AVB_MAX_NUM_CMDLINE_SUBST) {
428 /* The list is full. Currently dynamic growth of this list is not supported.
429 */
430 return AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
431 }
432
433 /* Construct the token to replace in the command line based on the partition
434 * name. For partition 'foo', this will be '$(AVB_FOO_ROOT_DIGEST)'.
435 */
436 out_cmdline_subst->tokens[list_index] =
437 avb_strdupv(kDigestSubPrefix, part_name, kDigestSubSuffix, NULL);
438 if (out_cmdline_subst->tokens[list_index] == NULL) {
439 goto fail;
440 }
441 avb_uppercase(out_cmdline_subst->tokens[list_index]);
442
443 /* The digest value is hex encoded when inserted in the command line. */
444 out_cmdline_subst->values[list_index] = avb_bin2hex(digest, digest_size);
445 if (out_cmdline_subst->values[list_index] == NULL) {
446 goto fail;
447 }
448
449 out_cmdline_subst->size++;
450 return AVB_SLOT_VERIFY_RESULT_OK;
451
452fail:
453 if (out_cmdline_subst->tokens[list_index]) {
454 avb_free(out_cmdline_subst->tokens[list_index]);
455 }
456 if (out_cmdline_subst->values[list_index]) {
457 avb_free(out_cmdline_subst->values[list_index]);
458 }
459 return AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
460}