/* * 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 #include #include #include #include #include #include #include #include #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 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 static 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 (slot_len < sizeof(*md)) { fprintf(stderr, "Slot too small!\n"); return 1; } 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");