| /* 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 <common/image.h> |
| |
| #include <fcntl.h> |
| #include <stddef.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <gelf.h> |
| #include <libelf.h> |
| |
| #include <common/publickey.h> |
| |
| #include <openssl/bn.h> |
| #include <openssl/evp.h> |
| #include <openssl/pem.h> |
| #include <openssl/rand.h> |
| #include <openssl/rsa.h> |
| |
| #include <common/signed_header.h> |
| |
| #include <string> |
| |
| using namespace std; |
| |
| extern bool FLAGS_verbose; |
| |
| #define VERBOSE(...) \ |
| do { \ |
| if (FLAGS_verbose) fprintf(stderr, __VA_ARGS__); \ |
| } while (0) |
| #define WARN(...) \ |
| do { \ |
| fprintf(stderr, __VA_ARGS__); \ |
| } while (0) |
| #define FATAL(...) \ |
| do { \ |
| fprintf(stderr, __VA_ARGS__); \ |
| abort(); \ |
| } while (0) |
| |
| static const int FLASH_START = 0x4000; |
| static const int FLASH_END = FLASH_START + 512 * 1024; |
| |
| Image::Image() |
| : success_(true), |
| low_(FLASH_END - FLASH_START), |
| high_(0), |
| base_(0), |
| ro_base_(FLASH_END * 16), |
| rx_base_(FLASH_END * 16), |
| ro_max_(0), |
| rx_max_(0) { |
| memset(mem_, 0xff, sizeof(mem_)); // default memory content |
| } |
| |
| void Image::randomize() { |
| int fd = open("/dev/urandom", O_RDONLY); |
| if (fd >= 0) { |
| read(fd, mem_, sizeof(mem_)); |
| close(fd); |
| } |
| } |
| |
| bool Image::fromElf(const string& filename) { |
| Elf* elf = NULL; |
| Elf_Scn* scn = NULL; |
| GElf_Shdr shdr; |
| GElf_Phdr phdr; |
| |
| char* base_ptr = NULL; |
| struct stat elf_stats; |
| |
| bool result = false; |
| |
| int fd; |
| |
| if ((fd = open(filename.c_str(), O_RDONLY)) < 0) { |
| WARN("failed to open '%s'\n", filename.c_str()); |
| goto fail; |
| } |
| if ((fstat(fd, &elf_stats)) < 0) { |
| WARN("cannot stat '%s'\n", filename.c_str()); |
| goto fail; |
| } |
| |
| // printf("Elf filesize: %lu\n", elf_stats.st_size); |
| |
| if ((base_ptr = (char*)malloc(elf_stats.st_size)) == NULL) { |
| WARN("cannot malloc %lu\n", elf_stats.st_size); |
| goto fail; |
| } |
| |
| if (read(fd, base_ptr, elf_stats.st_size) < elf_stats.st_size) { |
| WARN("cannot read from '%s'\n", filename.c_str()); |
| goto fail; |
| } |
| |
| // Sniff content for sanity |
| if (*(uint32_t*)base_ptr != 0x464c457f) { |
| // WARN("'%s' is not elf file\n", filename); |
| goto fail; |
| } |
| |
| if (elf_version(EV_CURRENT) == EV_NONE) { |
| WARN("Warning: elf library is out of date!\n"); |
| } |
| |
| elf = elf_begin(fd, ELF_C_READ, NULL); |
| |
| // Infer minimal rx segment from section headers. |
| while ((scn = elf_nextscn(elf, scn)) != 0) { |
| gelf_getshdr(scn, &shdr); |
| |
| VERBOSE("type %08x; flags %08lx ", shdr.sh_type, shdr.sh_flags); |
| VERBOSE("%08lx(@%08lx)[%08lx] align %lu\n", shdr.sh_addr, shdr.sh_offset, |
| shdr.sh_size, shdr.sh_addralign); |
| |
| // Ignore sections that are not alloc |
| if (!(shdr.sh_flags & SHF_ALLOC)) { |
| continue; |
| } |
| |
| // Ignore sections that are not exec |
| if (!(shdr.sh_flags & SHF_EXECINSTR)) { |
| continue; |
| } |
| |
| // Ignore sections outside our flash range |
| if (shdr.sh_addr < FLASH_START * 16 || |
| shdr.sh_addr + shdr.sh_size >= FLASH_END * 16) { |
| continue; |
| } |
| |
| // Track rx boundaries |
| if (shdr.sh_addr < rx_base_) { |
| rx_base_ = shdr.sh_addr; |
| } |
| if (shdr.sh_addr + shdr.sh_size > rx_max_) { |
| rx_max_ = shdr.sh_addr + shdr.sh_size; |
| } |
| } |
| |
| // Load image per program headers and track total ro segment |
| for (int index = 0; gelf_getphdr(elf, index, &phdr); ++index) { |
| VERBOSE("phdr %08lx(@%08lx) [%08lx/%08lx]", phdr.p_vaddr, phdr.p_paddr, |
| phdr.p_filesz, phdr.p_memsz); |
| |
| // Ignore sections outside our flash range |
| if (phdr.p_paddr < FLASH_START * 16 || |
| phdr.p_paddr + phdr.p_filesz >= FLASH_END * 16) { |
| VERBOSE(" (outside flash, skipped)\n"); |
| continue; |
| } |
| |
| // Ignore p_offset 0, which ELF hdr; cannot be legit. |
| if (phdr.p_offset == 0) { |
| VERBOSE(" (offset 0, ignoring)\n"); |
| continue; |
| } |
| |
| VERBOSE("\n"); |
| |
| // Track ro boundaries |
| if (phdr.p_paddr < ro_base_) { |
| ro_base_ = phdr.p_paddr; |
| } |
| if (phdr.p_paddr + phdr.p_filesz > ro_max_) { |
| ro_max_ = phdr.p_paddr + phdr.p_filesz; |
| } |
| |
| // Copy data into image |
| for (size_t n = 0; n < phdr.p_filesz; ++n) { |
| store(phdr.p_paddr + n - FLASH_START * 16, base_ptr[phdr.p_offset + n]); |
| } |
| } |
| |
| low_ &= ~2047; |
| base_ = low_; |
| |
| // Set ro_base to start, so app can read its own header. |
| ro_base_ = base_ + FLASH_START * 16; |
| // Set rx_base to just past header, where interrupt vectors are, |
| // since fetching a vector gets done on the I bus. |
| rx_base_ = ro_base_ + sizeof(SignedHeader); |
| |
| high_ = ((high_ + 2047) / 2048) * 2048; // Round image to multiple of 2K. |
| |
| VERBOSE("Rounded image size %lu\n", size()); |
| VERBOSE("ro_base %08lx..%08lx\n", ro_base_, ro_max_); |
| VERBOSE("rx_base %08lx..%08lx\n", rx_base_, rx_max_); |
| |
| result = true; |
| |
| fail: |
| if (elf) elf_end(elf); |
| if (base_ptr) free(base_ptr); |
| if (fd >= 0) close(fd); |
| |
| return result; |
| } |
| |
| bool Image::fromIntelHex(const string& filename, bool withSignature) { |
| bool isRam = false; |
| int seg = 0; |
| |
| FILE* fp = fopen(filename.c_str(), "rt"); |
| if (fp != NULL) { |
| char line[BUFSIZ]; |
| while (fgets(line, sizeof(line), fp)) { |
| if (strchr(line, '\r')) *strchr(line, '\r') = 0; |
| if (strchr(line, '\n')) *strchr(line, '\n') = 0; |
| if (line[0] != ':') continue; // assume comment line |
| if (strlen(line) < 9) { |
| WARN("short record %s", line); |
| success_ = false; |
| continue; |
| } |
| if (line[7] != '0') { |
| WARN("unknown record type %s", line); |
| success_ = false; |
| } else |
| switch (line[8]) { |
| case '1': { // 01 eof |
| } break; |
| case '2': { // 02 segment |
| if (!strncmp(line, ":02000002", 9)) { |
| char* p = line + 9; |
| int s = parseWord(&p); |
| if (s != 0x1000) { |
| if (s >= FLASH_START && s <= FLASH_END) { |
| seg = s - FLASH_START; |
| // WARN("at segment %04x\n", seg); |
| } else { |
| WARN("data should in range %x-%x: %s\n", FLASH_START, |
| FLASH_END, line); |
| success_ = false; |
| } |
| } |
| } |
| isRam = !strcmp(line, ":020000021000EC"); |
| } break; |
| case '0': { // 00 data |
| char* p = line + 1; |
| int len = parseByte(&p); |
| int adr = parseWord(&p); |
| parseByte(&p); |
| while (len--) { |
| if (isRam) { |
| int v = parseByte(&p); |
| if (v != 0) { |
| WARN("WARNING: non-zero RAM byte %02x at %04x\n", v, adr); |
| } |
| ++adr; |
| } else { |
| store((seg * 16) + adr++, parseByte(&p)); |
| } |
| } |
| } break; |
| case '3': { // 03 entry point |
| } break; |
| default: { |
| WARN("unknown record type %s", line); |
| success_ = false; |
| } break; |
| } |
| } |
| fclose(fp); |
| } else { |
| WARN("failed to open file '%s'\n", filename.c_str()); |
| success_ = false; |
| } |
| |
| if (success_) { |
| static_assert(sizeof(SignedHeader) == 1024, |
| "SignedHeader should be 1024 bytes"); |
| if (withSignature) { |
| // signed images start on 2K boundary. |
| if ((low_ & 2047) != 0) { |
| WARN("signed images should start on 2K boundary, not %08x\n", low_); |
| } |
| base_ = low_; |
| } else { |
| // unsigned images start on odd 1K boundary. |
| if ((low_ & 2047) != 1024) { |
| WARN("unsigned images should start odd 1K boundary, not %08x\n", low_); |
| } |
| base_ = low_ - sizeof(SignedHeader); |
| } |
| } |
| |
| if (success_) { |
| VERBOSE("low %08x, high %08x\n", FLASH_START * 16 + low_, |
| FLASH_START * 16 + high_); |
| // Round image to multiple of 2K. |
| high_ = ((high_ + 2047) / 2048) * 2048; |
| ro_base_ = FLASH_START * 16 + base_; |
| rx_base_ = FLASH_START * 16 + base_; |
| ro_max_ = FLASH_START * 16 + base_ + size(); |
| rx_max_ = FLASH_START * 16 + base_ + size(); |
| VERBOSE("base %08lx, size %08lx\n", ro_base_, size()); |
| } |
| |
| return success_; |
| } |
| |
| Image::~Image() {} |
| |
| void Image::toIntelHex(FILE* fout) const { |
| for (int i = base_; i < high_; i += 16) { |
| // spit out segment record at start of segment. |
| if (!((i - base_) & 0xffff)) { |
| int s = FLASH_START + (base_ >> 4) + ((i - base_) >> 4); |
| fprintf(fout, ":02000002%04X%02X\n", s, |
| (~((2 + 2 + (s >> 8)) & 255) + 1) & 255); |
| } |
| // spit out data records, 16 bytes each. |
| fprintf(fout, ":10%04X00", (i - base_) & 0xffff); |
| int crc = 16 + (((i - base_) >> 8) & 255) + ((i - base_) & 255); |
| for (int n = 0; n < 16; ++n) { |
| fprintf(fout, "%02X", mem_[i + n]); |
| crc += mem_[i + n]; |
| } |
| fprintf(fout, "%02X", (~(crc & 255) + 1) & 255); |
| fprintf(fout, "\n"); |
| } |
| } |
| |
| void Image::fillPattern(uint32_t pattern) { |
| for (int i = high_ - base_; i < 512 * 1024 - 2048; i += 4) { |
| *(uint32_t*)(mem_ + i) = pattern; |
| } |
| high_ = 512 * 1024 - 2048; |
| } |
| |
| void Image::fillRandom() { |
| srand(time(NULL)); |
| for (int i = high_ - base_; i < 512 * 1024 - 2048; i += 4) { |
| *(uint32_t*)(mem_ + i) = rand(); |
| } |
| high_ = 512 * 1024 - 2048; |
| } |
| |
| void Image::generate(const std::string& filename, bool hex_output) const { |
| FILE* fout = fopen(filename.c_str(), "w"); |
| if (!fout) return; |
| |
| if (hex_output) |
| toIntelHex(fout); |
| else // TODO: we don't expect write to fail, can be made more robust. |
| fwrite(mem_ + base_, 1, high_ - base_, fout); |
| fclose(fout); |
| } |
| |
| int Image::nibble(char n) { |
| switch (n) { |
| case '0': |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| return n - '0'; |
| case 'a': |
| case 'A': |
| return 10; |
| case 'b': |
| case 'B': |
| return 11; |
| case 'c': |
| case 'C': |
| return 12; |
| case 'd': |
| case 'D': |
| return 13; |
| case 'e': |
| case 'E': |
| return 14; |
| case 'f': |
| case 'F': |
| return 15; |
| default: |
| WARN("bad hex digit '%c'\n", n); |
| success_ = false; |
| return 0; |
| } |
| } |
| |
| int Image::parseByte(char** p) { |
| int result = nibble(**p); |
| result *= 16; |
| (*p)++; |
| result |= nibble(**p); |
| (*p)++; |
| return result; |
| } |
| |
| int Image::parseWord(char** p) { |
| int result = parseByte(p); |
| result *= 256; |
| result |= parseByte(p); |
| return result; |
| } |
| |
| void Image::store(int adr, int v) { |
| if (adr < 0 || (size_t)(adr) >= sizeof(mem_)) { |
| WARN("illegal adr %04x\n", adr); |
| success_ = false; |
| return; |
| } |
| // VERBOSE("mem_[0x%08x]=0x%02x\n", adr, v&255); |
| mem_[adr] = v; |
| if (adr > high_) high_ = adr; |
| if (adr < low_) low_ = adr; |
| } |
| |
| bool Image::sign(PublicKey& key, const SignedHeader* input_hdr, |
| const uint32_t fuses[FUSE_MAX], const uint32_t info[INFO_MAX], |
| const string& hashesFilename) { |
| BIGNUM* sig = NULL; |
| SignedHeader* hdr = (SignedHeader*)(&mem_[base_]); |
| SHA256_CTX sha256; |
| int result; |
| |
| // List of hashes we actually sign. |
| struct { |
| uint8_t img_hash[SHA256_DIGEST_LENGTH]; |
| uint8_t fuses_hash[SHA256_DIGEST_LENGTH]; |
| uint8_t info_hash[SHA256_DIGEST_LENGTH]; |
| } hashes; |
| |
| memcpy(hdr, input_hdr, sizeof(SignedHeader)); |
| |
| hdr->image_size = this->size(); |
| |
| // Fill in key traits |
| hdr->keyid = key.n0inv(); |
| key.modToArray(hdr->key, key.rwords()); |
| |
| // Hash fuses |
| SHA256_Init(&sha256); |
| SHA256_Update(&sha256, fuses, FUSE_MAX * sizeof(uint32_t)); |
| SHA256_Final(hashes.fuses_hash, &sha256); |
| |
| hdr->fuses_chk_ = (hashes.fuses_hash[0] << 0) | (hashes.fuses_hash[1] << 8) | |
| (hashes.fuses_hash[2] << 16) | (hashes.fuses_hash[3] << 24); |
| |
| // Hash info |
| SHA256_Init(&sha256); |
| SHA256_Update(&sha256, info, INFO_MAX * sizeof(uint32_t)); |
| SHA256_Final(hashes.info_hash, &sha256); |
| |
| hdr->info_chk_ = (hashes.info_hash[0] << 0) | (hashes.info_hash[1] << 8) | |
| (hashes.info_hash[2] << 16) | (hashes.info_hash[3] << 24); |
| |
| // Hash img |
| int size = this->size() - offsetof(SignedHeader, tag); |
| SHA256_Init(&sha256); |
| SHA256_Update(&sha256, &hdr->tag, size); |
| SHA256_Final(hashes.img_hash, &sha256); |
| |
| hdr->img_chk_ = (hashes.img_hash[0] << 0) | (hashes.img_hash[1] << 8) | |
| (hashes.img_hash[2] << 16) | (hashes.img_hash[3] << 24); |
| |
| // Dump out values for comparing against boot_rom |
| VERBOSE("Himg ="); |
| for (size_t i = 0; i < sizeof(hashes.img_hash); ++i) { |
| VERBOSE("%02x", hashes.img_hash[i]); |
| } |
| VERBOSE("\n"); |
| |
| VERBOSE("Hfss ="); |
| for (size_t i = 0; i < sizeof(hashes.fuses_hash); ++i) { |
| VERBOSE("%02x", hashes.fuses_hash[i]); |
| } |
| VERBOSE("\n"); |
| |
| VERBOSE("Hinf ="); |
| for (size_t i = 0; i < sizeof(hashes.info_hash); ++i) { |
| VERBOSE("%02x", hashes.info_hash[i]); |
| } |
| VERBOSE("\n"); |
| |
| if (!hashesFilename.empty()) { |
| // Write hashes to file for subsequent extraneous (re)signing. |
| int fd = open(hashesFilename.c_str(), O_CREAT | O_TRUNC | O_RDWR, 0600); |
| if (fd >= 0) { |
| write(fd, &hashes, sizeof(hashes)); |
| close(fd); |
| } |
| } |
| |
| sig = BN_bin2bn((uint8_t*)(hdr->signature), sizeof(hdr->signature), NULL); |
| |
| result = key.sign(&hashes, sizeof(hashes), &sig); |
| |
| if (result != 1) { |
| WARN("key.sign: %d\n", result); |
| } else { |
| key.toArray(hdr->signature, key.rwords(), sig); |
| } |
| |
| if (sig) BN_free(sig); |
| |
| return result == 1; |
| } |