blob: f489851ee0d551efa14cda05d3a92a0a67b7dc72 [file] [log] [blame]
/* Copyright 2015 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 <assert.h>
#include <fcntl.h>
#include <getopt.h>
#include <inttypes.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <common/image.h>
#include <common/publickey.h>
#include <common/signed_header.h>
#ifdef HAVE_JSON
#include <rapidjson/document.h>
#endif
#include <map>
#include <string>
#include <vector>
#include <fstream>
#include <iostream>
#include <sstream>
#include <libxml/parser.h>
#include <libxml/tree.h>
using namespace std;
#define VERBOSE(...) \
do { \
if (FLAGS_verbose) fprintf(stderr, __VA_ARGS__); \
} while (0)
#define FATAL(...) \
do { \
fprintf(stderr, __VA_ARGS__); \
abort(); \
} while (0)
bool FLAGS_verbose = false;
bool FLAGS_cros = false;
int last_logical_offset = -1;
int fuse_index = 0;
// Brute xml parsing.
// Find HashItem w/ key == name, return val field, recursively.
static xmlChar* get_val(xmlNodePtr node, const char* key) {
xmlNode* cur_node = NULL;
xmlChar* val = NULL;
for (cur_node = node->children; cur_node; cur_node = cur_node->next) {
if (!strcmp("HashItem", (const char*)(cur_node->name))) {
// Hardcode parse <HashItem><Key>key</Key><Val>val</Val></HashItem>
xmlNodePtr key_node = cur_node->children->next;
xmlNodePtr val_node = cur_node->children->next->next->next;
xmlChar* keyName = xmlNodeGetContent(key_node);
xmlChar* valData = xmlNodeGetContent(val_node);
if (!strcmp(key, (const char*)keyName)) {
// Found our key, save val and done.
xmlFree(keyName);
val = valData;
break;
}
xmlFree(valData);
xmlFree(keyName);
}
val = get_val(cur_node, key);
if (val) {
// Found our key somewhere deeper down; done.
break;
}
}
return val;
}
static bool get_fuse(xmlNodePtr a_node, map<string, uint32_t>* ids,
map<string, uint32_t>* bits) {
bool result = false;
// Interested in <HashType>
if (strcmp("HashType", (const char*)(a_node->name))) {
return result;
}
// Values we are interested in.
xmlChar* RegName = get_val(a_node, "RegName");
xmlChar* Width = get_val(a_node, "Width");
xmlChar* FuseLogicalOffset = get_val(a_node, "FuseLogicalOffset");
// Track 1024 fuses at most.
int fuseLogicalOffset = atoi((const char*)FuseLogicalOffset);
if (fuseLogicalOffset >= last_logical_offset) {
last_logical_offset = fuseLogicalOffset;
ids->insert(make_pair((const char*)RegName, fuse_index++));
bits->insert(make_pair((const char*)RegName, atoi((const char*)Width)));
} else {
// Logical offset is regressing; assume we saw all the fuses.
// There are multiple sections that list all the fuses in the xml;
// we only care about parsing them once.
result = true;
}
xmlFree(FuseLogicalOffset);
xmlFree(Width);
xmlFree(RegName);
return result;
}
static bool find_fuses(xmlNodePtr a_node, map<string, uint32_t>* ids,
map<string, uint32_t>* bits) {
xmlNode* cur_node = NULL;
bool done = false;
for (cur_node = a_node; !done && cur_node; cur_node = cur_node->next) {
xmlChar* content = NULL;
if (cur_node->type == XML_TEXT_NODE &&
(content = xmlNodeGetContent(cur_node)) != NULL) {
if (!strcmp("FuseLogicalOffset", (const char*)content)) {
// Found a likely fuse definition section; collect it.
done = get_fuse(a_node->parent->parent->parent, ids, bits);
}
}
if (content) xmlFree(content);
if (!done && cur_node->children) {
done = find_fuses(cur_node->children, ids, bits);
}
}
return done;
}
static bool find_default_reg_value(xmlNodePtr a_node, const string& regname,
string* result) {
xmlNode* cur_node = NULL;
bool done = false;
for (cur_node = a_node; !done && cur_node; cur_node = cur_node->next) {
xmlChar* content = NULL;
if (cur_node->type == XML_TEXT_NODE &&
(content = xmlNodeGetContent(cur_node)) != NULL) {
if (!strcmp(regname.c_str(), (const char*)content)) {
xmlChar* val = get_val(cur_node->parent->parent->parent, "Default");
if (val) {
result->assign((const char*)val);
xmlFree(val);
done = true;
}
}
}
if (content) xmlFree(content);
if (!done && cur_node->children) {
done = find_default_reg_value(cur_node->children, regname, result);
}
}
return done;
}
// Read XML, populate two maps, name -> val
bool readXML(const string& filename, map<string, uint32_t>* ids,
map<string, uint32_t>* bits, uint32_t* p4cl) {
bool result = false;
LIBXML_TEST_VERSION
xmlDocPtr doc = xmlReadFile(filename.c_str(), NULL, 0);
if (doc) {
result = find_fuses(xmlDocGetRootElement(doc), ids, bits);
string p4clStr;
result &= find_default_reg_value(xmlDocGetRootElement(doc),
"SWDP_P4_LAST_SYNC", &p4clStr);
if (result) {
*p4cl = atoi(p4clStr.c_str());
}
xmlFreeDoc(doc);
}
xmlCleanupParser();
xmlMemoryDump();
return result;
}
// Read JSON, populate map, name -> val
bool readJSON(const string& filename, string* tag,
map<string, uint32_t>* values, map<string, uint32_t>* fusemap,
map<string, uint32_t>* infomap) {
bool result = false;
#ifdef HAVE_JSON
ifstream ifs(filename.c_str());
if (ifs) {
// Touch up a bit to allow for comments.
// Beware: we drop everything past and including '//' from any line.
// Thus '//' cannot be substring of any value..
string s;
while (ifs) {
string line;
getline(ifs, line);
size_t slash = line.find("//");
if (slash != string::npos) {
line.erase(slash);
}
s.append(line);
}
// Try parse.
rapidjson::Document d;
if (d.Parse(s.c_str()).HasParseError()) {
FATAL("JSON %s[%lu]: parse error\n", filename.c_str(),
d.GetErrorOffset());
} else {
#define CHECKVALUE(x) \
do { \
if (!d.HasMember(x)) { \
FATAL("manifest is lacking field '%s'\n", x); \
}; \
} while (0)
#define GETVALUE(x) \
do { \
if (!d.HasMember(x)) { \
FATAL("manifest is lacking field '%s'\n", x); \
}; \
(*values)[x] = d[x].GetInt(); \
} while (0)
CHECKVALUE("fuses");
const rapidjson::Document::ValueType& fuses = d["fuses"];
for (rapidjson::Value::ConstMemberIterator it = fuses.MemberBegin();
it != fuses.MemberEnd(); ++it) {
(*fusemap)[it->name.GetString()] = it->value.GetInt();
}
CHECKVALUE("info");
const rapidjson::Document::ValueType& infos = d["info"];
for (rapidjson::Value::ConstMemberIterator it = infos.MemberBegin();
it != infos.MemberEnd(); ++it) {
(*infomap)[it->name.GetString()] = it->value.GetInt();
}
GETVALUE("keyid");
GETVALUE("p4cl");
GETVALUE("epoch");
GETVALUE("major");
GETVALUE("minor");
GETVALUE("applysec");
GETVALUE("config1");
GETVALUE("err_response");
GETVALUE("expect_response");
GETVALUE("timestamp");
CHECKVALUE("tag");
const rapidjson::Document::ValueType& Tag = d["tag"];
tag->assign(Tag.GetString());
result = true;
#undef GETVALUE
#undef CHECKVALUE
}
}
#endif // HAVE_JSON
return result;
}
string inputFilename;
string outputFilename;
string keyFilename;
string xmlFilename;
string jsonFilename;
string outputFormat;
string signatureFilename;
string hashesFilename;
bool fillPattern = false;
uint32_t pattern = -1;
bool fillRandom = false;
void usage(int argc, char* argv[]) {
fprintf(stderr,
"Usage: %s options\n"
"--input=$elf-filename\n"
"--output=output-filename\n"
"--key=$pem-filename\n"
"[--b] ignored option, could be included for forward compatibility\n"
"[--cros] to sign for the ChromeOS realm w/o manifest\n"
"[--xml=$xml-filename] typically 'havenTop.xml'\n"
"[--json=$json-filename] the signing manifest\n"
"[--format=bin|hex] output file format, hex is default\n"
"[--signature=$sig-filename] replace signature with file content\n"
"[--hashes=$hashes-filename] destination file for intermediary "
"hashes to be signed\n"
"[--randomfill] to pad image to 512K with random bits\n"
"[--patternfill=N] to pad image to 512K with pattern N\n"
"[--verbose]\n",
argv[0]);
}
int getOptions(int argc, char* argv[]) {
static struct option long_options[] = {
// name, has_arg
{"b", no_argument, NULL, 'b'},
{"cros", no_argument, NULL, 'c'},
{"format", required_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h'},
{"input", required_argument, NULL, 'i'},
{"json", required_argument, NULL, 'j'},
{"key", required_argument, NULL, 'k'},
{"output", required_argument, NULL, 'o'},
{"verbose", no_argument, NULL, 'v'},
{"xml", required_argument, NULL, 'x'},
{"signature", required_argument, NULL, 's'},
{"hashes", required_argument, NULL, 'H'},
{"randomfill", no_argument, NULL, 'r'},
{"patternfill", required_argument, NULL, 'p'},
{"writefuses", required_argument, NULL, 'w'},
{0, 0, 0, 0}};
int c, option_index = 0;
outputFormat.assign("hex");
while ((c = getopt_long(argc, argv, "i:o:p:k:x:j:f:s:H:bchvr", long_options,
&option_index)) != -1) {
switch (c) {
case 0:
fprintf(stderr, "option %s", long_options[option_index].name);
if (optarg) fprintf(stderr, " with arg %s", optarg);
fprintf(stderr, "\n");
break;
case 'b':
break;
case 'c':
FLAGS_cros = true;
break;
case 'i':
inputFilename.assign(optarg);
break;
case 'o':
outputFilename.assign(optarg);
break;
case 'k':
keyFilename.assign(optarg);
break;
case 'x':
xmlFilename.assign(optarg);
break;
case 's':
signatureFilename.assign(optarg);
break;
case 'j':
jsonFilename.assign(optarg);
break;
case 'f':
outputFormat.assign(optarg);
break;
case 'H':
hashesFilename.assign(optarg);
break;
case 'r':
fillRandom = true;
break;
case 'p':
fillPattern = true;
pattern = strtoul(optarg, NULL, 0);
break;
case 'h':
usage(argc, argv);
return 1;
case 'v':
FLAGS_verbose = true;
break;
case '?':
// getopt_long printed error
return 1;
}
}
if (inputFilename.empty() || outputFilename.empty() || keyFilename.empty() ||
((outputFormat != "bin") && (outputFormat != "hex"))) {
usage(argc, argv);
return 1;
}
return 0;
}
int main(int argc, char* argv[]) {
if (getOptions(argc, argv)) {
exit(1);
}
PublicKey key(keyFilename);
if (!key.ok()) return -1;
// Load elf.
Image image;
if (!image.fromElf(inputFilename)) return -2;
if (fillPattern) image.fillPattern(pattern);
if (fillRandom) image.fillRandom();
SignedHeader hdr;
hdr.keyid = key.n0inv();
hdr.ro_base = image.ro_base();
hdr.ro_max = image.ro_max();
hdr.rx_base = image.rx_base();
hdr.rx_max =
image.rx_max() +
12; // TODO: m3 instruction prefetch sets off GLOBALSEC when too tight
// make sure these are nops or such?
hdr.timestamp_ = time(NULL);
// Parse signing manifest.
map<string, uint32_t> values;
map<string, uint32_t> fuses;
map<string, uint32_t> infos;
string tag;
if (jsonFilename.empty()) {
// Defaults, in case no JSON
values.insert(make_pair("keyid", key.n0inv()));
values.insert(make_pair("epoch", 0x1337));
}
// Hardcoded expectation. Can be overwritten in JSON w/ new explicit value.
fuses["FW_DEFINED_DATA_EXTRA_BLK6"] = 0;
if (!jsonFilename.empty() &&
!readJSON(jsonFilename, &tag, &values, &fuses, &infos)) {
FATAL("Failed to read JSON from '%s'\n", jsonFilename.c_str());
}
// Fill in more of hdr, per manifest values
for (map<string, uint32_t>::const_iterator it = values.begin();
it != values.end(); ++it) {
VERBOSE("%s : %u\n", it->first.c_str(), it->second);
}
hdr.p4cl_ = values["p4cl"];
hdr.epoch_ = values["epoch"];
hdr.major_ = values["major"];
hdr.minor_ = values["minor"];
hdr.applysec_ = values["applysec"];
hdr.config1_ = values["config1"];
hdr.err_response_ = values["err_response"];
hdr.expect_response_ = values["expect_response"];
if (values["timestamp"]) hdr.timestamp_ = values["timestamp"];
VERBOSE("timestamp: %ld\n", hdr.timestamp_);
// Check keyId.
if (values["keyid"] != hdr.keyid) {
FATAL("mismatched keyid JSON %d vs. key %d\n", values["keyid"], hdr.keyid);
}
if (FLAGS_cros) {
if (!tag.empty()) {
FATAL("--cros whilst also specifying tag per manifest is a no go");
}
tag = "\x01\x00\x00\x00"; // cros realm identifier in rwr[0]
}
// Fill in tag.
VERBOSE("tag: \"%s\"\n", tag.c_str());
strncpy((char*)(&hdr.tag), tag.c_str(), sizeof(hdr.tag));
// List the specific fuses and values.
VERBOSE("care about %lu fuses:\n", fuses.size());
for (map<string, uint32_t>::const_iterator it = fuses.begin();
it != fuses.end(); ++it) {
VERBOSE("fuse '%s' should have value %u\n", it->first.c_str(), it->second);
}
// Parse xml.
map<string, uint32_t> fuse_ids;
map<string, uint32_t> fuse_bits;
uint32_t xml_p4cl = 0;
if (!xmlFilename.empty() &&
!readXML(xmlFilename, &fuse_ids, &fuse_bits, &xml_p4cl)) {
FATAL("Failed to read XML from '%s'\n", xmlFilename.c_str());
}
if (values["p4cl"] != xml_p4cl) {
FATAL("mismatching p4cl: xml %u vs. json %u\n", xml_p4cl, values["p4cl"]);
}
VERBOSE("found %lu fuse definitions\n", fuse_ids.size());
assert(fuse_ids.size() < FUSE_MAX);
if (fuse_ids.size() != 0) {
// Make sure FW_DEFINED_DATA_EXTRA_BLK6 is still at 125, width 3.
assert(fuse_ids["FW_DEFINED_DATA_EXTRA_BLK6"] == 125);
assert(fuse_bits["FW_DEFINED_DATA_EXTRA_BLK6"] == 5);
}
// Whether we loaded xml or not, hardcode FW_DEFINED_DATA_EXTRA_BLK6
fuse_ids["FW_DEFINED_DATA_EXTRA_BLK6"] = 125;
fuse_bits["FW_DEFINED_DATA_EXTRA_BLK6"] = 5;
for (map<string, uint32_t>::const_iterator it = fuse_ids.begin();
it != fuse_ids.end(); ++it) {
VERBOSE("fuse '%s' at %u, width %u\n", it->first.c_str(), it->second,
fuse_bits[it->first]);
}
// Compute fuse_values array, according to manifest and xml.
uint32_t fuse_values[FUSE_MAX];
for (size_t i = 0; i < FUSE_MAX; ++i) fuse_values[i] = FUSE_IGNORE;
for (map<string, uint32_t>::const_iterator x = fuses.begin();
x != fuses.end(); ++x) {
map<string, uint32_t>::const_iterator it = fuse_ids.find(x->first);
if (it == fuse_ids.end()) {
FATAL("cannot find definition for fuse '%s'\n", x->first.c_str());
}
uint32_t idx = it->second;
assert(idx < FUSE_MAX);
uint32_t mask = (1ul << fuse_bits[x->first]) - 1;
if ((x->second & mask) != x->second) {
FATAL("specified fuse value too large\n");
}
uint32_t val = FUSE_PADDING & ~mask;
val |= x->second;
fuse_values[idx] = val;
hdr.markFuse(idx);
}
// Print out fuse hash input.
VERBOSE("expected fuse state:\n");
for (size_t i = 0; i < FUSE_MAX; ++i) {
VERBOSE("%08x ", fuse_values[i]);
}
VERBOSE("\n");
// Compute info_values array, according to manifest.
uint32_t info_values[INFO_MAX];
for (size_t i = 0; i < INFO_MAX; ++i) info_values[i] = INFO_IGNORE;
for (map<string, uint32_t>::const_iterator x = infos.begin();
x != infos.end(); ++x) {
uint32_t index = atoi(x->first.c_str());
assert(index < INFO_MAX);
info_values[index] ^= x->second;
hdr.markInfo(index);
}
// TODO: read values from JSON or implement version logic here.
// Print out info hash input.
VERBOSE("expected info state:\n");
for (size_t i = 0; i < INFO_MAX; ++i) {
VERBOSE("%08x ", info_values[i]);
}
VERBOSE("\n");
if (!signatureFilename.empty()) {
int fd = ::open(signatureFilename.c_str(), O_RDONLY);
if (fd > 0) {
int n = ::read(fd, hdr.signature, sizeof(hdr.signature));
::close(fd);
if (n != sizeof(hdr.signature))
FATAL("cannot read from '%s'\n", signatureFilename.c_str());
VERBOSE("provided signature\n");
} else {
FATAL("cannot open '%s'\n", signatureFilename.c_str());
}
}
// Sign image.
if (image.sign(key, &hdr, fuse_values, info_values, hashesFilename)) {
image.generate(outputFilename, outputFormat == "hex");
} else {
FATAL("failed to sign\n");
}
return 0;
}