| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2022 MediaTek Inc. All Rights Reserved. |
| * |
| * Author: Weijie Gao <weijie.gao@mediatek.com> |
| * |
| * Tool for modifying bootargs in dtb for dm-verity |
| */ |
| |
| #ifdef _MSC_VER |
| #define _CRT_SECURE_NO_WARNINGS |
| #define _CRT_NONSTDC_NO_WARNINGS |
| #endif /* _MSC_VER */ |
| |
| #define _GNU_SOURCE |
| |
| #include <stdio.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <stdarg.h> |
| #include <malloc.h> |
| #include <ctype.h> |
| #include <errno.h> |
| #include <libfdt.h> |
| |
| #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) |
| |
| struct kvpair { |
| char *name; |
| char *value; |
| bool quoted_value; |
| bool deleted; |
| }; |
| |
| static void *fdt; |
| static char *veritysummary; |
| static uint32_t fdt_len, summary_len; |
| |
| static struct kvpair *summary_lines; |
| static uint32_t summary_line_count; |
| |
| static struct kvpair *bootargs_items; |
| static uint32_t bootargs_item_count; |
| |
| #ifdef _MSC_VER |
| int vasprintf(char **strp, const char *format, va_list ap) |
| { |
| int len = _vscprintf(format, ap); |
| if (len == -1) |
| return -1; |
| char *str = (char *)malloc((size_t)len + 1); |
| if (!str) |
| return -1; |
| int retval = vsnprintf(str, len + 1, format, ap); |
| if (retval == -1) { |
| free(str); |
| return -1; |
| } |
| *strp = str; |
| return retval; |
| } |
| |
| int asprintf(char **strp, const char *format, ...) |
| { |
| va_list ap; |
| va_start(ap, format); |
| int retval = vasprintf(strp, format, ap); |
| va_end(ap); |
| return retval; |
| } |
| |
| static char *strndup(const char *str, size_t n) |
| { |
| size_t len = strlen(str); |
| char *s; |
| |
| if (len < n) |
| return strdup(str); |
| |
| s = malloc(n + 1); |
| if (!s) |
| return NULL; |
| |
| memcpy(s, str, n); |
| s[n] = 0; |
| |
| return s; |
| } |
| #endif /* _MSC_VER */ |
| |
| static int read_file(const char *file, void **buffer, uint32_t *filelen) |
| { |
| size_t rdlen; |
| int ret = 0; |
| uint8_t *ptr; |
| FILE *f; |
| long len; |
| |
| f = fopen(file, "rb"); |
| if (!f) { |
| fprintf(stderr, "Failed to open file '%s', error %d\n", file, errno); |
| return -errno; |
| } |
| |
| ret = fseek(f, 0, SEEK_END); |
| if (ret < 0) { |
| ret = ferror(f); |
| fprintf(stderr, "fseek() failed, error %d\n", ret); |
| goto cleanup; |
| } |
| |
| len = ftell(f); |
| if (len < 0) { |
| ret = ferror(f); |
| fprintf(stderr, "ftell() failed, error %d\n", ret); |
| goto cleanup; |
| } |
| |
| ret = fseek(f, 0, SEEK_SET); |
| if (ret < 0) { |
| ret = ferror(f); |
| fprintf(stderr, "fseek() failed, error %d\n", ret); |
| goto cleanup; |
| } |
| |
| ptr = malloc(len + 1); |
| if (!ptr) { |
| ret = ferror(f); |
| fprintf(stderr, "Failed to allocate memory\n"); |
| goto cleanup; |
| } |
| |
| rdlen = fread(ptr, 1, len, f); |
| if (rdlen != len) { |
| ret = ferror(f); |
| fprintf(stderr, "Failed to read file, error %d\n", ret); |
| free(ptr); |
| *buffer = NULL; |
| goto cleanup; |
| } |
| |
| ptr[len] = 0; |
| |
| *buffer = (void *)ptr; |
| |
| if (filelen) |
| *filelen = len; |
| |
| cleanup: |
| fclose(f); |
| return ret; |
| } |
| |
| static int write_file(const char *file, const void *buffer, uint32_t len) |
| { |
| size_t wrlen; |
| FILE *f; |
| int ret; |
| |
| f = fopen(file, "wb"); |
| if (!f) { |
| fprintf(stderr, "Failed to open file '%s', error %d\n", file, errno); |
| return -errno; |
| } |
| |
| wrlen = fwrite(buffer, 1, len, f); |
| ret = ferror(f); |
| fclose(f); |
| |
| if (wrlen != len) { |
| fprintf(stderr, "Failed to write file, error %d\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static struct kvpair *kvpair_find(struct kvpair *pairs, uint32_t count, const char *name) |
| { |
| uint32_t i; |
| |
| for (i = 0; i < count; i++) { |
| if (!strcmp(pairs[i].name, name)) |
| return &pairs[i]; |
| } |
| |
| return NULL; |
| } |
| |
| /* find the first line-ending char in a string */ |
| static inline char *strcrlf(char *str) |
| { |
| while (*str) { |
| if (*str == '\r' || *str == '\n') |
| return str; |
| str++; |
| } |
| |
| return NULL; |
| } |
| |
| /* find the first NULL char in a string */ |
| static inline char *strnull(char *str, size_t len) |
| { |
| while (len) { |
| if (!*str) |
| return str; |
| str++; |
| len--; |
| } |
| |
| return NULL; |
| } |
| |
| /* trim all whitespace chars at the beginning of a string by moving pointer */ |
| static inline char *ltrim(const char *str) |
| { |
| while (*str == 0x20 || *str == '\t') |
| str++; |
| |
| return (char *)str; |
| } |
| |
| /* trim all whitespace chars at the end of a string. chars will be changed */ |
| static inline void rtrim_len(char *str, size_t len) |
| { |
| char *eos = str + len - 1; |
| |
| while (eos >= str) { |
| if (*eos != 0x20 && *eos != '\t') |
| break; |
| |
| *eos = 0; |
| eos--; |
| } |
| } |
| |
| /* derived from libkvcutil */ |
| static int parse_verity_summary(void) |
| { |
| char *ptr = veritysummary, *pcrlf, *psep, *peol, *pkey; |
| uint32_t lines = 1; |
| |
| /* |
| * first step: count total number of lines |
| * this make sure we only allocate memory once |
| */ |
| do { |
| pcrlf = strcrlf(ptr); |
| if (!pcrlf) |
| break; |
| |
| /* |
| * rules of line splitting: |
| * - CR + LF: treat as one line (Windows/DOS) |
| * - CR + ^LF: Mac |
| * - LF: Unix/Linux |
| */ |
| if (pcrlf[0] == '\r' && pcrlf[1] == '\n') |
| ptr = pcrlf + 2; |
| else |
| ptr = pcrlf + 1; |
| |
| lines++; |
| } while (1); |
| |
| /* allocate memory to store parsed lines */ |
| summary_lines = calloc(lines, sizeof(*summary_lines)); |
| if (!summary_lines) { |
| fprintf(stderr, "Failed to allocate memory for summary parsing\n"); |
| return -ENOMEM; |
| } |
| |
| /* second step: split lines and parse keys and values */ |
| ptr = veritysummary; |
| |
| do { |
| peol = strcrlf(ptr); |
| pcrlf = peol; |
| |
| /* split a line and record its line-ending */ |
| if (pcrlf) { |
| if (pcrlf[0] == '\r' && pcrlf[1] == '\n') { |
| pcrlf[0] = 0; |
| pcrlf += 2; |
| } else { |
| pcrlf[0] = 0; |
| pcrlf += 1; |
| } |
| } else { |
| /* CR/LF not found. should be the last line */ |
| peol = strnull(ptr, summary_len - (ptr - veritysummary) + 1); |
| pcrlf = peol + 1; |
| } |
| |
| /* trim leading spaces of key */ |
| pkey = ltrim(ptr); |
| |
| psep = strchr(pkey, ':'); |
| |
| if (!psep) { |
| /* line has no ':' */ |
| goto next_line; |
| } |
| |
| psep[0] = 0; |
| |
| /* trim trailing spaces of key */ |
| rtrim_len(pkey, psep - pkey); |
| |
| /* trim white spaces of value */ |
| psep = ltrim(psep + 1); |
| rtrim_len(psep, peol - psep); |
| |
| summary_lines[summary_line_count].name = pkey; |
| summary_lines[summary_line_count].value = psep; |
| summary_line_count++; |
| |
| next_line: |
| ptr = pcrlf; |
| if (ptr >= veritysummary + summary_len) |
| break; |
| } while (1); |
| |
| return 0; |
| } |
| |
| static const char *verity_summary_find(const char *name) |
| { |
| struct kvpair *p = kvpair_find(summary_lines, summary_line_count, name); |
| |
| if (p) |
| return p->value; |
| |
| return NULL; |
| } |
| |
| /** |
| * skip_spaces - Removes leading whitespace from @str. |
| * @str: The string to be stripped. |
| * |
| * Returns a pointer to the first non-whitespace character in @str. |
| */ |
| char *skip_spaces(const char *str) |
| { |
| while (isspace(*str)) |
| ++str; |
| return (char *)str; |
| } |
| |
| /* derived from linux kernel */ |
| static const char *get_arg_next(const char *args, const char **param, size_t *keylen) |
| { |
| unsigned int i, equals = 0; |
| int in_quote = 0; |
| |
| args = skip_spaces(args); |
| if (!*args) |
| return NULL; |
| |
| if (*args == '"') { |
| args++; |
| in_quote = 1; |
| } |
| |
| for (i = 0; args[i]; i++) { |
| if (isspace(args[i]) && !in_quote) |
| break; |
| |
| if (equals == 0) { |
| if (args[i] == '=') |
| equals = i; |
| } |
| |
| if (args[i] == '"') |
| in_quote = !in_quote; |
| } |
| |
| *param = args; |
| |
| if (equals) |
| *keylen = equals; |
| else |
| *keylen = i; |
| |
| return args + i; |
| } |
| |
| static int parse_bootargs(const char *bootargs) |
| { |
| const char *n = bootargs, *p; |
| size_t len, keylen; |
| uint32_t i = 0; |
| |
| while (1) { |
| n = get_arg_next(n, &p, &keylen); |
| if (!n) |
| break; |
| |
| bootargs_item_count++; |
| } |
| |
| if (!bootargs_item_count) |
| return 0; |
| |
| bootargs_items = calloc(bootargs_item_count, sizeof(*bootargs_items)); |
| if (!bootargs_items) { |
| fprintf(stderr, "Failed to allocate memory for summary parsing\n"); |
| return -ENOMEM; |
| } |
| |
| n = bootargs; |
| |
| while (1) { |
| n = get_arg_next(n, &p, &keylen); |
| if (!n) |
| break; |
| |
| len = n - p; |
| |
| bootargs_items[i].name = strndup(p, keylen); |
| if (!bootargs_items[i].name) |
| return -ENOMEM; |
| |
| if (keylen < len) { |
| if (p[keylen + 1] == '\"') { |
| bootargs_items[i].value = strndup(p + keylen + 2, len - keylen - 3); |
| bootargs_items[i].quoted_value = true; |
| } else { |
| bootargs_items[i].value = strndup(p + keylen + 1, len - keylen - 1); |
| } |
| |
| if (!bootargs_items[i].value) |
| return -ENOMEM; |
| } |
| |
| i++; |
| } |
| |
| return 0; |
| } |
| |
| static const char *bootargs_find(const char *name) |
| { |
| struct kvpair *p = kvpair_find(bootargs_items, bootargs_item_count, name); |
| |
| if (p) |
| return p->value; |
| |
| return NULL; |
| } |
| |
| static char *strconcat(char *dst, const char *src) |
| { |
| while (*src) |
| *dst++ = *src++; |
| |
| return dst; |
| } |
| |
| static char *merge_bootargs(struct kvpair *new_pairs, uint32_t num) |
| { |
| struct kvpair *kvp; |
| uint32_t i, len = 0; |
| char *val; |
| char *str, *p; |
| bool quoted; |
| |
| /* Estimate new booargs size */ |
| for (i = 0; i < bootargs_item_count; i++) { |
| len += strlen(bootargs_items[i].name); |
| if (bootargs_items[i].value) |
| len += strlen(bootargs_items[i].value); |
| len += 4; /* =, "", space */ |
| } |
| |
| for (i = 0; i < num; i++) { |
| len += strlen(new_pairs[i].name); |
| if (new_pairs[i].value) |
| len += strlen(new_pairs[i].value); |
| len += 4; /* =, "", space */ |
| } |
| |
| str = calloc(len + 1, 1); |
| if (!str) |
| return NULL; |
| |
| /* Merge */ |
| p = str; |
| |
| /* Existed or overwritten */ |
| for (i = 0; i < bootargs_item_count; i++) { |
| kvp = kvpair_find(new_pairs, num, bootargs_items[i].name); |
| if (kvp) { |
| val = kvp->value; |
| quoted = kvp->quoted_value; |
| kvp->deleted = true; |
| } else { |
| val = bootargs_items[i].value; |
| quoted = bootargs_items[i].quoted_value; |
| } |
| |
| p = strconcat(p, bootargs_items[i].name); |
| if (val) { |
| *p++ = '='; |
| |
| if (quoted) |
| *p++ = '\"'; |
| |
| p = strconcat(p, val); |
| |
| if (quoted) |
| *p++ = '\"'; |
| } |
| |
| *p++ = ' '; |
| } |
| |
| /* New */ |
| for (i = 0; i < num; i++) { |
| if (new_pairs[i].deleted) |
| continue; |
| |
| p = strconcat(p, new_pairs[i].name); |
| if (new_pairs[i].value) { |
| *p++ = '='; |
| |
| if (new_pairs[i].quoted_value) |
| *p++ = '\"'; |
| |
| p = strconcat(p, new_pairs[i].value); |
| |
| if (new_pairs[i].quoted_value) |
| *p++ = '\"'; |
| } |
| |
| *p++ = ' '; |
| } |
| |
| p[-1] = 0; |
| |
| return str; |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| const char *datablocks, *datablock_size, *hashblock_size, *hash_algo, *salt, *root_hash; |
| const char *bootargs, *rootdev; |
| int ret, nodeoffset, len; |
| struct kvpair dmpairs[2]; |
| uint32_t datablocks_num; |
| char *dmstr, *nfdt; |
| size_t nlen; |
| |
| if (argc < 4) { |
| printf("Usage: <summary> <fdt-in> <fdt-out> [override-root]\n"); |
| return 0; |
| } |
| |
| ret = read_file(argv[1], (void **)&veritysummary, &summary_len); |
| if (ret) |
| return 1; |
| |
| ret = read_file(argv[2], &fdt, &fdt_len); |
| if (ret) |
| return 2; |
| |
| if (parse_verity_summary()) |
| return 3; |
| |
| /* find "/chosen" node. */ |
| nodeoffset = fdt_subnode_offset(fdt, 0, "chosen"); |
| if (nodeoffset < 0) { |
| fprintf(stderr, "Node `chosen' not found\n"); |
| return 4; |
| } |
| |
| bootargs = fdt_getprop(fdt, nodeoffset, "bootargs", &len); |
| if (!bootargs) { |
| fprintf(stderr, "Property `bootargs' not found\n"); |
| return 5; |
| } |
| |
| parse_bootargs(bootargs); |
| |
| /* find rootdev */ |
| rootdev = bootargs_find("root"); |
| if (!rootdev) { |
| if (argc == 4 || !strlen(argv[4])) { |
| fprintf(stderr, "`root' not found in `bootargs`\n"); |
| return 6; |
| } |
| |
| if (strchr(argv[4], ' ') || strchr(argv[4], '\t') || strcrlf(argv[4])) { |
| fprintf(stderr, "Overrided `root' must not contain whitespace\n"); |
| return 6; |
| } |
| |
| rootdev = argv[4]; |
| } |
| |
| /* No dm-mod.create is expected */ |
| if (bootargs_find("dm-mod.create")) { |
| fprintf(stderr, "Found unexpected `dm-mod.create' in `bootargs'\n"); |
| return 7; |
| } |
| |
| /* Assemble `dm-mod.create' */ |
| datablocks = verity_summary_find("Data blocks"); |
| datablock_size = verity_summary_find("Data block size"); |
| hashblock_size = verity_summary_find("Hash block size"); |
| hash_algo = verity_summary_find("Hash algorithm"); |
| salt = verity_summary_find("Salt"); |
| root_hash = verity_summary_find("Root hash"); |
| |
| if (!datablocks || !datablock_size || !hashblock_size || !hash_algo || !salt || !root_hash) { |
| fprintf(stderr, "Incomplete summary of veritysetup\n"); |
| return 8; |
| } |
| |
| datablocks_num = strtoul(datablocks, NULL, 0); |
| if (datablocks_num == ULONG_MAX) { |
| fprintf(stderr, "Data blocks is invalid\n"); |
| return 9; |
| } |
| |
| ret = asprintf(&dmstr, "dm-verity,,,ro,0 %u verity 1 %s %s %s %s %u %u %s %s %s", |
| datablocks_num * 8, rootdev, rootdev, datablock_size, hashblock_size, |
| datablocks_num, datablocks_num + 1, hash_algo, root_hash, salt); |
| if (ret < 0) { |
| fprintf(stderr, "Failed to format `dm-mod.create'\n"); |
| return 9; |
| } |
| |
| /* Assemble new bootargs */ |
| memset(dmpairs, 0, sizeof(dmpairs)); |
| |
| dmpairs[0].name = "root"; |
| dmpairs[0].value = "/dev/dm-0"; |
| |
| dmpairs[1].name = "dm-mod.create"; |
| dmpairs[1].value = dmstr; |
| dmpairs[1].quoted_value = true; |
| |
| bootargs = merge_bootargs(dmpairs, ARRAY_SIZE(dmpairs)); |
| if (!bootargs) { |
| fprintf(stderr, "Failed to merge new bootargs\n"); |
| return 10; |
| } |
| |
| /* Resize dtb buffer */ |
| nlen = strlen(bootargs) + 1; |
| nfdt = realloc(fdt, fdt_len + nlen); |
| if (!nfdt) { |
| fprintf(stderr, "Failed to extend fdt buffer\n"); |
| return 11; |
| } |
| |
| memset((uint8_t *)nfdt + fdt_len, 0, nlen); |
| |
| fdt = nfdt; |
| |
| /* Modify bootagrs in dtb */ |
| ret = fdt_open_into(fdt, fdt, fdt_totalsize(fdt) + nlen); |
| if (ret) { |
| fprintf(stderr, "Failed to extend fdt size\n"); |
| return 11; |
| } |
| |
| ret = fdt_setprop(fdt, nodeoffset, "bootargs", bootargs, nlen); |
| if (ret < 0) { |
| fprintf(stderr, "Failed to set new `bootargs'\n"); |
| return 12; |
| } |
| |
| /* Change the fdt header to reflect the correct size */ |
| fdt_set_totalsize(fdt, fdt_off_dt_strings(fdt) + fdt_size_dt_strings(fdt)); |
| |
| /* Save fdt */ |
| ret = write_file(argv[3], fdt, fdt_totalsize(fdt)); |
| if (ret) |
| return 13; |
| |
| return 0; |
| } |