blob: ccd70946069e50a94499bbdcb910a9ae29ffd677 [file] [log] [blame]
developerd92db592022-04-13 17:09:05 +08001// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright (C) 2022 MediaTek Inc. All Rights Reserved.
4 *
5 * Author: Weijie Gao <weijie.gao@mediatek.com>
6 *
7 * Tool for modifying bootargs in dtb for dm-verity
8 */
9
10#ifdef _MSC_VER
11#define _CRT_SECURE_NO_WARNINGS
12#define _CRT_NONSTDC_NO_WARNINGS
13#endif /* _MSC_VER */
14
15#define _GNU_SOURCE
16
17#include <stdio.h>
18#include <stdint.h>
19#include <stdlib.h>
20#include <stdarg.h>
21#include <malloc.h>
22#include <ctype.h>
23#include <errno.h>
24#include <libfdt.h>
25
26#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
27
28struct kvpair {
29 char *name;
30 char *value;
31 bool quoted_value;
32 bool deleted;
33};
34
35static void *fdt;
36static char *veritysummary;
37static uint32_t fdt_len, summary_len;
38
39static struct kvpair *summary_lines;
40static uint32_t summary_line_count;
41
42static struct kvpair *bootargs_items;
43static uint32_t bootargs_item_count;
44
45#ifdef _MSC_VER
46int vasprintf(char **strp, const char *format, va_list ap)
47{
48 int len = _vscprintf(format, ap);
49 if (len == -1)
50 return -1;
51 char *str = (char *)malloc((size_t)len + 1);
52 if (!str)
53 return -1;
54 int retval = vsnprintf(str, len + 1, format, ap);
55 if (retval == -1) {
56 free(str);
57 return -1;
58 }
59 *strp = str;
60 return retval;
61}
62
63int asprintf(char **strp, const char *format, ...)
64{
65 va_list ap;
66 va_start(ap, format);
67 int retval = vasprintf(strp, format, ap);
68 va_end(ap);
69 return retval;
70}
71
72static char *strndup(const char *str, size_t n)
73{
74 size_t len = strlen(str);
75 char *s;
76
77 if (len < n)
78 return strdup(str);
79
80 s = malloc(n + 1);
81 if (!s)
82 return NULL;
83
84 memcpy(s, str, n);
85 s[n] = 0;
86
87 return s;
88}
89#endif /* _MSC_VER */
90
91static int read_file(const char *file, void **buffer, uint32_t *filelen)
92{
93 size_t rdlen;
94 int ret = 0;
95 uint8_t *ptr;
96 FILE *f;
97 long len;
98
99 f = fopen(file, "rb");
100 if (!f) {
101 fprintf(stderr, "Failed to open file '%s', error %d\n", file, errno);
102 return -errno;
103 }
104
105 ret = fseek(f, 0, SEEK_END);
106 if (ret < 0) {
107 ret = ferror(f);
108 fprintf(stderr, "fseek() failed, error %d\n", ret);
109 goto cleanup;
110 }
111
112 len = ftell(f);
113 if (len < 0) {
114 ret = ferror(f);
115 fprintf(stderr, "ftell() failed, error %d\n", ret);
116 goto cleanup;
117 }
118
119 ret = fseek(f, 0, SEEK_SET);
120 if (ret < 0) {
121 ret = ferror(f);
122 fprintf(stderr, "fseek() failed, error %d\n", ret);
123 goto cleanup;
124 }
125
126 ptr = malloc(len + 1);
127 if (!ptr) {
128 ret = ferror(f);
129 fprintf(stderr, "Failed to allocate memory\n");
130 goto cleanup;
131 }
132
133 rdlen = fread(ptr, 1, len, f);
134 if (rdlen != len) {
135 ret = ferror(f);
136 fprintf(stderr, "Failed to read file, error %d\n", ret);
137 free(ptr);
138 *buffer = NULL;
139 goto cleanup;
140 }
141
142 ptr[len] = 0;
143
144 *buffer = (void *)ptr;
145
146 if (filelen)
147 *filelen = len;
148
149cleanup:
150 fclose(f);
151 return ret;
152}
153
154static int write_file(const char *file, const void *buffer, uint32_t len)
155{
156 size_t wrlen;
157 FILE *f;
158 int ret;
159
160 f = fopen(file, "wb");
161 if (!f) {
162 fprintf(stderr, "Failed to open file '%s', error %d\n", file, errno);
163 return -errno;
164 }
165
166 wrlen = fwrite(buffer, 1, len, f);
167 ret = ferror(f);
168 fclose(f);
169
170 if (wrlen != len) {
171 fprintf(stderr, "Failed to write file, error %d\n", ret);
172 return ret;
173 }
174
175 return 0;
176}
177
178static struct kvpair *kvpair_find(struct kvpair *pairs, uint32_t count, const char *name)
179{
180 uint32_t i;
181
182 for (i = 0; i < count; i++) {
183 if (!strcmp(pairs[i].name, name))
184 return &pairs[i];
185 }
186
187 return NULL;
188}
189
190/* find the first line-ending char in a string */
191static inline char *strcrlf(char *str)
192{
193 while (*str) {
194 if (*str == '\r' || *str == '\n')
195 return str;
196 str++;
197 }
198
199 return NULL;
200}
201
202/* find the first NULL char in a string */
203static inline char *strnull(char *str, size_t len)
204{
205 while (len) {
206 if (!*str)
207 return str;
208 str++;
209 len--;
210 }
211
212 return NULL;
213}
214
215/* trim all whitespace chars at the beginning of a string by moving pointer */
216static inline char *ltrim(const char *str)
217{
218 while (*str == 0x20 || *str == '\t')
219 str++;
220
221 return (char *)str;
222}
223
224/* trim all whitespace chars at the end of a string. chars will be changed */
225static inline void rtrim_len(char *str, size_t len)
226{
227 char *eos = str + len - 1;
228
229 while (eos >= str) {
230 if (*eos != 0x20 && *eos != '\t')
231 break;
232
233 *eos = 0;
234 eos--;
235 }
236}
237
238/* derived from libkvcutil */
239static int parse_verity_summary(void)
240{
241 char *ptr = veritysummary, *pcrlf, *psep, *peol, *pkey;
242 uint32_t lines = 1;
243
244 /*
245 * first step: count total number of lines
246 * this make sure we only allocate memory once
247 */
248 do {
249 pcrlf = strcrlf(ptr);
250 if (!pcrlf)
251 break;
252
253 /*
254 * rules of line splitting:
255 * - CR + LF: treat as one line (Windows/DOS)
256 * - CR + ^LF: Mac
257 * - LF: Unix/Linux
258 */
259 if (pcrlf[0] == '\r' && pcrlf[1] == '\n')
260 ptr = pcrlf + 2;
261 else
262 ptr = pcrlf + 1;
263
264 lines++;
265 } while (1);
266
267 /* allocate memory to store parsed lines */
268 summary_lines = calloc(lines, sizeof(*summary_lines));
269 if (!summary_lines) {
270 fprintf(stderr, "Failed to allocate memory for summary parsing\n");
271 return -ENOMEM;
272 }
273
274 /* second step: split lines and parse keys and values */
275 ptr = veritysummary;
276
277 do {
278 peol = strcrlf(ptr);
279 pcrlf = peol;
280
281 /* split a line and record its line-ending */
282 if (pcrlf) {
283 if (pcrlf[0] == '\r' && pcrlf[1] == '\n') {
284 pcrlf[0] = 0;
285 pcrlf += 2;
286 } else {
287 pcrlf[0] = 0;
288 pcrlf += 1;
289 }
290 } else {
291 /* CR/LF not found. should be the last line */
292 peol = strnull(ptr, summary_len - (ptr - veritysummary) + 1);
293 pcrlf = peol + 1;
294 }
295
296 /* trim leading spaces of key */
297 pkey = ltrim(ptr);
298
299 psep = strchr(pkey, ':');
300
301 if (!psep) {
302 /* line has no ':' */
303 goto next_line;
304 }
305
306 psep[0] = 0;
307
308 /* trim trailing spaces of key */
309 rtrim_len(pkey, psep - pkey);
310
311 /* trim white spaces of value */
312 psep = ltrim(psep + 1);
313 rtrim_len(psep, peol - psep);
314
315 summary_lines[summary_line_count].name = pkey;
316 summary_lines[summary_line_count].value = psep;
317 summary_line_count++;
318
319 next_line:
320 ptr = pcrlf;
321 if (ptr >= veritysummary + summary_len)
322 break;
323 } while (1);
324
325 return 0;
326}
327
328static const char *verity_summary_find(const char *name)
329{
330 struct kvpair *p = kvpair_find(summary_lines, summary_line_count, name);
331
332 if (p)
333 return p->value;
334
335 return NULL;
336}
337
338/**
339 * skip_spaces - Removes leading whitespace from @str.
340 * @str: The string to be stripped.
341 *
342 * Returns a pointer to the first non-whitespace character in @str.
343 */
344char *skip_spaces(const char *str)
345{
346 while (isspace(*str))
347 ++str;
348 return (char *)str;
349}
350
351/* derived from linux kernel */
352static const char *get_arg_next(const char *args, const char **param, size_t *keylen)
353{
354 unsigned int i, equals = 0;
355 int in_quote = 0;
356
357 args = skip_spaces(args);
358 if (!*args)
359 return NULL;
360
361 if (*args == '"') {
362 args++;
363 in_quote = 1;
364 }
365
366 for (i = 0; args[i]; i++) {
367 if (isspace(args[i]) && !in_quote)
368 break;
369
370 if (equals == 0) {
371 if (args[i] == '=')
372 equals = i;
373 }
374
375 if (args[i] == '"')
376 in_quote = !in_quote;
377 }
378
379 *param = args;
380
381 if (equals)
382 *keylen = equals;
383 else
384 *keylen = i;
385
386 return args + i;
387}
388
389static int parse_bootargs(const char *bootargs)
390{
391 const char *n = bootargs, *p;
392 size_t len, keylen;
393 uint32_t i = 0;
394
395 while (1) {
396 n = get_arg_next(n, &p, &keylen);
397 if (!n)
398 break;
399
400 bootargs_item_count++;
401 }
402
403 if (!bootargs_item_count)
404 return 0;
405
406 bootargs_items = calloc(bootargs_item_count, sizeof(*bootargs_items));
407 if (!bootargs_items) {
408 fprintf(stderr, "Failed to allocate memory for summary parsing\n");
409 return -ENOMEM;
410 }
411
412 n = bootargs;
413
414 while (1) {
415 n = get_arg_next(n, &p, &keylen);
416 if (!n)
417 break;
418
419 len = n - p;
420
421 bootargs_items[i].name = strndup(p, keylen);
422 if (!bootargs_items[i].name)
423 return -ENOMEM;
424
425 if (keylen < len) {
426 if (p[keylen + 1] == '\"') {
427 bootargs_items[i].value = strndup(p + keylen + 2, len - keylen - 3);
428 bootargs_items[i].quoted_value = true;
429 } else {
430 bootargs_items[i].value = strndup(p + keylen + 1, len - keylen - 1);
431 }
432
433 if (!bootargs_items[i].value)
434 return -ENOMEM;
435 }
436
437 i++;
438 }
439
440 return 0;
441}
442
443static const char *bootargs_find(const char *name)
444{
445 struct kvpair *p = kvpair_find(bootargs_items, bootargs_item_count, name);
446
447 if (p)
448 return p->value;
449
450 return NULL;
451}
452
453static char *strconcat(char *dst, const char *src)
454{
455 while (*src)
456 *dst++ = *src++;
457
458 return dst;
459}
460
461static char *merge_bootargs(struct kvpair *new_pairs, uint32_t num)
462{
463 struct kvpair *kvp;
464 uint32_t i, len = 0;
465 char *val;
466 char *str, *p;
467 bool quoted;
468
469 /* Estimate new booargs size */
470 for (i = 0; i < bootargs_item_count; i++) {
471 len += strlen(bootargs_items[i].name);
472 if (bootargs_items[i].value)
473 len += strlen(bootargs_items[i].value);
474 len += 4; /* =, "", space */
475 }
476
477 for (i = 0; i < num; i++) {
478 len += strlen(new_pairs[i].name);
479 if (new_pairs[i].value)
480 len += strlen(new_pairs[i].value);
481 len += 4; /* =, "", space */
482 }
483
484 str = calloc(len + 1, 1);
485 if (!str)
486 return NULL;
487
488 /* Merge */
489 p = str;
490
491 /* Existed or overwritten */
492 for (i = 0; i < bootargs_item_count; i++) {
493 kvp = kvpair_find(new_pairs, num, bootargs_items[i].name);
494 if (kvp) {
495 val = kvp->value;
496 quoted = kvp->quoted_value;
497 kvp->deleted = true;
498 } else {
499 val = bootargs_items[i].value;
500 quoted = bootargs_items[i].quoted_value;
501 }
502
503 p = strconcat(p, bootargs_items[i].name);
504 if (val) {
505 *p++ = '=';
506
507 if (quoted)
508 *p++ = '\"';
509
510 p = strconcat(p, val);
511
512 if (quoted)
513 *p++ = '\"';
514 }
515
516 *p++ = ' ';
517 }
518
519 /* New */
520 for (i = 0; i < num; i++) {
521 if (new_pairs[i].deleted)
522 continue;
523
524 p = strconcat(p, new_pairs[i].name);
525 if (new_pairs[i].value) {
526 *p++ = '=';
527
528 if (new_pairs[i].quoted_value)
529 *p++ = '\"';
530
531 p = strconcat(p, new_pairs[i].value);
532
533 if (new_pairs[i].quoted_value)
534 *p++ = '\"';
535 }
536
537 *p++ = ' ';
538 }
539
540 p[-1] = 0;
541
542 return str;
543}
544
545int main(int argc, char *argv[])
546{
547 const char *datablocks, *datablock_size, *hashblock_size, *hash_algo, *salt, *root_hash;
548 const char *bootargs, *rootdev;
549 int ret, nodeoffset, len;
550 struct kvpair dmpairs[2];
551 uint32_t datablocks_num;
552 char *dmstr, *nfdt;
553 size_t nlen;
554
555 if (argc < 4) {
556 printf("Usage: <summary> <fdt-in> <fdt-out> [override-root]\n");
557 return 0;
558 }
559
560 ret = read_file(argv[1], (void **)&veritysummary, &summary_len);
561 if (ret)
562 return 1;
563
564 ret = read_file(argv[2], &fdt, &fdt_len);
565 if (ret)
566 return 2;
567
568 if (parse_verity_summary())
569 return 3;
570
571 /* find "/chosen" node. */
572 nodeoffset = fdt_subnode_offset(fdt, 0, "chosen");
573 if (nodeoffset < 0) {
574 fprintf(stderr, "Node `chosen' not found\n");
575 return 4;
576 }
577
578 bootargs = fdt_getprop(fdt, nodeoffset, "bootargs", &len);
579 if (!bootargs) {
580 fprintf(stderr, "Property `bootargs' not found\n");
581 return 5;
582 }
583
584 parse_bootargs(bootargs);
585
586 /* find rootdev */
587 rootdev = bootargs_find("root");
588 if (!rootdev) {
589 if (argc == 4 || !strlen(argv[4])) {
590 fprintf(stderr, "`root' not found in `bootargs`\n");
591 return 6;
592 }
593
594 if (strchr(argv[4], ' ') || strchr(argv[4], '\t') || strcrlf(argv[4])) {
595 fprintf(stderr, "Overrided `root' must not contain whitespace\n");
596 return 6;
597 }
598
599 rootdev = argv[4];
600 }
601
602 /* No dm-mod.create is expected */
603 if (bootargs_find("dm-mod.create")) {
604 fprintf(stderr, "Found unexpected `dm-mod.create' in `bootargs'\n");
605 return 7;
606 }
607
608 /* Assemble `dm-mod.create' */
609 datablocks = verity_summary_find("Data blocks");
610 datablock_size = verity_summary_find("Data block size");
611 hashblock_size = verity_summary_find("Hash block size");
612 hash_algo = verity_summary_find("Hash algorithm");
613 salt = verity_summary_find("Salt");
614 root_hash = verity_summary_find("Root hash");
615
616 if (!datablocks || !datablock_size || !hashblock_size || !hash_algo || !salt || !root_hash) {
617 fprintf(stderr, "Incomplete summary of veritysetup\n");
618 return 8;
619 }
620
621 datablocks_num = strtoul(datablocks, NULL, 0);
622 if (datablocks_num == ULONG_MAX) {
623 fprintf(stderr, "Data blocks is invalid\n");
624 return 9;
625 }
626
627 ret = asprintf(&dmstr, "dm-verity,,,ro,0 %u verity 1 %s %s %s %s %u %u %s %s %s",
628 datablocks_num * 8, rootdev, rootdev, datablock_size, hashblock_size,
629 datablocks_num, datablocks_num + 1, hash_algo, root_hash, salt);
630 if (ret < 0) {
631 fprintf(stderr, "Failed to format `dm-mod.create'\n");
632 return 9;
633 }
634
635 /* Assemble new bootargs */
636 memset(dmpairs, 0, sizeof(dmpairs));
637
638 dmpairs[0].name = "root";
639 dmpairs[0].value = "/dev/dm-0";
640
641 dmpairs[1].name = "dm-mod.create";
642 dmpairs[1].value = dmstr;
643 dmpairs[1].quoted_value = true;
644
645 bootargs = merge_bootargs(dmpairs, ARRAY_SIZE(dmpairs));
646 if (!bootargs) {
647 fprintf(stderr, "Failed to merge new bootargs\n");
648 return 10;
649 }
650
651 /* Resize dtb buffer */
652 nlen = strlen(bootargs) + 1;
653 nfdt = realloc(fdt, fdt_len + nlen);
654 if (!nfdt) {
655 fprintf(stderr, "Failed to extend fdt buffer\n");
656 return 11;
657 }
658
659 memset((uint8_t *)nfdt + fdt_len, 0, nlen);
660
661 fdt = nfdt;
662
663 /* Modify bootagrs in dtb */
664 ret = fdt_open_into(fdt, fdt, fdt_totalsize(fdt) + nlen);
665 if (ret) {
666 fprintf(stderr, "Failed to extend fdt size\n");
667 return 11;
668 }
669
670 ret = fdt_setprop(fdt, nodeoffset, "bootargs", bootargs, nlen);
671 if (ret < 0) {
672 fprintf(stderr, "Failed to set new `bootargs'\n");
673 return 12;
674 }
675
676 /* Change the fdt header to reflect the correct size */
677 fdt_set_totalsize(fdt, fdt_off_dt_strings(fdt) + fdt_size_dt_strings(fdt));
678
679 /* Save fdt */
680 ret = write_file(argv[3], fdt, fdt_totalsize(fdt));
681 if (ret)
682 return 13;
683
684 return 0;
685}