| /* |
| * Copyright 2016 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <getopt.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include "futility.h" |
| |
| enum { |
| OPT_HELP = 1000, |
| OPT_OFFSET, |
| }; |
| |
| static const struct option long_opts[] = { |
| {"help", 0, 0, OPT_HELP}, |
| {"offset", 1, 0, OPT_OFFSET}, |
| {NULL, 0, NULL, 0}, |
| }; |
| |
| static void print_help(int argc, char *argv[]) |
| { |
| printf("\nUsage: " MYNAME " %s FILE [OPTIONS]\n", argv[0]); |
| printf("\nOptions:\n"); |
| printf(" --offset <offset> Offset of cache within FILE\n"); |
| printf("\n"); |
| } |
| |
| struct mrc_metadata { |
| uint32_t signature; |
| uint32_t data_size; |
| uint16_t data_checksum; |
| uint16_t header_checksum; |
| uint32_t version; |
| } __attribute__((packed)); |
| |
| #define MRC_DATA_SIGNATURE (('M'<<0)|('R'<<8)|('C'<<16)|('D'<<24)) |
| #define REGF_BLOCK_SHIFT 4 |
| #define REGF_BLOCK_GRANULARITY (1 << REGF_BLOCK_SHIFT) |
| #define REGF_METADATA_BLOCK_SIZE REGF_BLOCK_GRANULARITY |
| #define REGF_UNALLOCATED_BLOCK 0xffff |
| |
| unsigned long compute_ip_checksum(const void *addr, unsigned long length) |
| { |
| const uint8_t *ptr; |
| volatile union { |
| uint8_t byte[2]; |
| uint16_t word; |
| } value; |
| unsigned long sum; |
| unsigned long i; |
| /* In the most straight forward way possible, |
| * compute an ip style checksum. |
| */ |
| sum = 0; |
| ptr = addr; |
| for(i = 0; i < length; i++) { |
| unsigned long v; |
| v = ptr[i]; |
| if (i & 1) { |
| v <<= 8; |
| } |
| /* Add the new value */ |
| sum += v; |
| /* Wrap around the carry */ |
| if (sum > 0xFFFF) { |
| sum = (sum + (sum >> 16)) & 0xFFFF; |
| } |
| } |
| value.byte[0] = sum & 0xff; |
| value.byte[1] = (sum >> 8) & 0xff; |
| return (~value.word) & 0xFFFF; |
| } |
| |
| static int verify_mrc_slot(struct mrc_metadata *md, unsigned long slot_len) |
| { |
| uint32_t header_checksum; |
| if (md->signature != MRC_DATA_SIGNATURE) { |
| fprintf(stderr, "MRC signature mismatch\n"); |
| return 1; |
| } |
| |
| fprintf(stderr, "MRC signature match.. successful\n"); |
| |
| if (md->data_size > slot_len) { |
| fprintf(stderr, "MRC cache size overflow\n"); |
| return 1; |
| } |
| |
| header_checksum = md->header_checksum; |
| md->header_checksum = 0; |
| |
| if (header_checksum != compute_ip_checksum(md, sizeof(*md))) { |
| fprintf(stderr, "MRC metadata header checksum mismatch\n"); |
| return 1; |
| } |
| |
| md->header_checksum = header_checksum; |
| |
| fprintf(stderr, "MRC metadata header checksum.. verified!\n"); |
| |
| if (md->data_checksum != compute_ip_checksum(&md[1], md->data_size)) { |
| fprintf(stderr, "MRC data checksum mismatch\n"); |
| return 1; |
| } |
| |
| fprintf(stderr, "MRC data checksum.. verified!\n"); |
| return 0; |
| } |
| |
| static int block_offset_unallocated(uint16_t offset) |
| { |
| return offset == REGF_UNALLOCATED_BLOCK; |
| } |
| |
| static uint8_t *get_next_mb(uint8_t *curr_mb) |
| { |
| return curr_mb + REGF_METADATA_BLOCK_SIZE; |
| } |
| |
| static int get_mrc_data_slot(uint16_t *mb, uint32_t *data_offset, |
| uint32_t *data_size) |
| { |
| uint16_t num_metadata_blocks = *mb; |
| |
| if (block_offset_unallocated(*mb)) { |
| fprintf(stderr, "MRC cache is empty!!\n"); |
| return 1; |
| } |
| |
| /* |
| * First block offset in metadata block tells the total number of |
| * metadata blocks. |
| * Currently, we expect only 1 metadata block to be used. |
| */ |
| if (num_metadata_blocks != 1) { |
| uint16_t *next_mb = (uint16_t *)get_next_mb((uint8_t *)mb); |
| if (!block_offset_unallocated(*next_mb)) { |
| fprintf(stderr, "More than 1 valid metadata block!!"); |
| return 1; |
| } |
| } |
| |
| /* |
| * RECOVERY_MRC_CACHE is expected to contain only one slot. Thus, there |
| * should be only one block offset present, indicating size of the MRC |
| * cache slot. |
| */ |
| mb++; |
| *data_offset = (1 << REGF_BLOCK_SHIFT) * num_metadata_blocks; |
| *data_size = (*mb - num_metadata_blocks) << REGF_BLOCK_SHIFT; |
| |
| mb++; |
| if (!block_offset_unallocated(*mb)) { |
| fprintf(stderr, "More than 1 slot in recovery mrc cache.\n"); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static int do_validate_rec_mrc(int argc, char *argv[]) |
| { |
| char *infile = NULL; |
| int parse_error = 0; |
| int fd, i, ret = 1; |
| uint32_t file_size; |
| uint8_t *buff; |
| uint32_t offset = 0; |
| uint32_t data_offset; |
| uint32_t data_size; |
| char *e; |
| |
| while (((i = getopt_long(argc, argv, ":", long_opts, NULL)) != -1) && |
| !parse_error) { |
| switch (i) { |
| case OPT_HELP: |
| print_help(argc, argv); |
| return 0; |
| case OPT_OFFSET: |
| offset = strtoul(optarg, &e, 0); |
| if (!*optarg || (e && *e)) { |
| fprintf(stderr, "Invalid --offset\n"); |
| parse_error = 1; |
| } |
| break; |
| default: |
| case '?': |
| parse_error = 1; |
| break; |
| } |
| } |
| |
| if (parse_error) { |
| print_help(argc, argv); |
| return 1; |
| } |
| |
| if ((argc - optind) < 1) { |
| fprintf(stderr, "You must specify an input FILE!\n"); |
| print_help(argc, argv); |
| return 1; |
| } else if ((argc - optind) != 1) { |
| fprintf(stderr, "Unexpected arguments!\n"); |
| print_help(argc, argv); |
| return 1; |
| } |
| |
| infile = argv[optind++]; |
| |
| fd = open(infile, O_RDONLY); |
| if (fd < 0) { |
| fprintf(stderr, "Cannot open %s:%s\n", infile, strerror(errno)); |
| return 1; |
| } |
| |
| if (futil_map_file(fd, MAP_RO, &buff, &file_size) != FILE_ERR_NONE) { |
| fprintf(stderr, "Cannot map file %s\n", infile); |
| close(fd); |
| return 1; |
| } |
| |
| if (offset > file_size) { |
| fprintf(stderr, "File size(0x%x) smaller than offset(0x%x)\n", |
| file_size, offset); |
| futil_unmap_file(fd, MAP_RO, buff, file_size); |
| close(fd); |
| return 1; |
| } |
| |
| if (get_mrc_data_slot((uint16_t *)(buff + offset), &data_offset, |
| &data_size)) { |
| fprintf(stderr, "Metadata block error\n"); |
| futil_unmap_file(fd, MAP_RO, buff, file_size); |
| close(fd); |
| return 1; |
| } |
| offset += data_offset; |
| |
| if ((file_size > offset) && ((file_size - offset) >= data_size)) |
| ret = verify_mrc_slot((struct mrc_metadata *)(buff + offset), |
| data_size); |
| else |
| fprintf(stderr, "Offset or data size greater than file size: " |
| "offset=0x%x, file size=0x%x, data_size=0x%x\n", |
| offset, file_size, data_size); |
| |
| if (futil_unmap_file(fd, MAP_RO, buff, file_size) != FILE_ERR_NONE) { |
| fprintf(stderr, "Failed to unmap file %s\n", infile); |
| ret = 1; |
| } |
| |
| close(fd); |
| return ret; |
| } |
| |
| DECLARE_FUTIL_COMMAND(validate_rec_mrc, do_validate_rec_mrc, VBOOT_VERSION_ALL, |
| "Validates content of Recovery MRC cache"); |