273 lines
6.2 KiB
C
273 lines
6.2 KiB
C
/* 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
|
|
|
|
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(%#x) smaller than offset(%#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=%#x, file size=%#x, data_size=%#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");
|