From 6ffd42548bf10cda13a01555ff4fa56d4213cdf2 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Thu, 22 Dec 2022 19:23:29 +0000 Subject: multiboot: Refactor boot data validation, add version numbers Instead of verifying the CRC before every access of the boot data, verify the CRC once at startup and set a flag to indicate the boot data is valid. Also add a framework to support multiple boot protocol versions. Firmware declares the maximum supported protocol version using a version byte in the boot data header. The bootloader chooses the highest version supported by it and the firmware when deciding what boot protocol to use. Change-Id: I810194625dc0833f026d2a23b8d64ed467fa6aca --- apps/debug_menu.c | 39 ++++++++----------- apps/main.c | 5 +++ firmware/SOURCES | 3 ++ firmware/common/bootdata.c | 74 ++++++++++++++++++++++++++++++++++++ firmware/common/multiboot.c | 55 +++++++++++++++++---------- firmware/common/rb-loader.c | 2 +- firmware/export/bootdata.h | 10 ++++- firmware/export/multiboot.h | 2 +- firmware/include/dircache_redirect.h | 6 +-- firmware/rolo.c | 9 ++--- firmware/target/arm/pp/mi4-loader.c | 2 +- 11 files changed, 149 insertions(+), 58 deletions(-) create mode 100644 firmware/common/bootdata.c diff --git a/apps/debug_menu.c b/apps/debug_menu.c index 5b73f8badd..5e2451e41b 100644 --- a/apps/debug_menu.c +++ b/apps/debug_menu.c @@ -2530,38 +2530,31 @@ static bool dbg_pic(void) #if defined(HAVE_BOOTDATA) && !defined(SIMULATOR) static bool dbg_boot_data(void) { - unsigned int crc = crc_32(boot_data.payload, boot_data.length, 0xffffffff); struct simplelist_info info; info.scroll_all = true; simplelist_info_init(&info, "Boot data", 1, NULL); simplelist_set_line_count(0); -#if defined(HAVE_MULTIBOOT) - char rootpath[MAX_PATH / 2] = RB_ROOT_CONTENTS_DIR; - int boot_volume = 0; - if(crc == boot_data.crc) + if (!boot_data_valid) { - boot_volume = boot_data.boot_volume; /* boot volume contained in uint8_t payload */ - int rtlen = get_redirect_dir(rootpath, sizeof(rootpath), boot_volume, "", ""); - while (rtlen > 0 && rootpath[--rtlen] == PATH_SEPCH) /* remove extra separators */ - rootpath[rtlen] = '\0'; + simplelist_addline("Boot data invalid"); + simplelist_addline("Magic[0]: %08lx", boot_data.magic[0]); + simplelist_addline("Magic[1]: %08lx", boot_data.magic[1]); + simplelist_addline("Length: %lu", boot_data.length); } - simplelist_addline("Boot Volume: <%lu>", boot_volume); - simplelist_addline("Root:"); - simplelist_addline("%s", rootpath); - simplelist_addline(""); -#endif + else + { + simplelist_addline("Boot data valid"); + simplelist_addline("Version: %d", (int)boot_data.version); + simplelist_addline("Boot volume: %d", (int)boot_data.boot_volume); + } + simplelist_addline("Bootdata RAW:"); - if (crc != boot_data.crc) - simplelist_addline("Magic: %.8s", boot_data.magic); - simplelist_addline("Length: %lu", boot_data.length); - simplelist_addline("CRC: %lx", boot_data.crc); - (crc == boot_data.crc) ? simplelist_addline("CRC: OK!") : - simplelist_addline("CRC: BAD"); - for (unsigned i = 0; i < boot_data.length; i += 4) + for (size_t i = 0; i < boot_data.length; i += 4) { - simplelist_addline("%02x: %02x %02x %02x %02x", i, boot_data.payload[i], - boot_data.payload[i+1], boot_data.payload[i+2], boot_data.payload[i+3]); + simplelist_addline("%02x: %02x %02x %02x %02x", i, + boot_data.payload[i + 0], boot_data.payload[i + 1], + boot_data.payload[i + 2], boot_data.payload[i + 3]); } return simplelist_show_list(&info); diff --git a/apps/main.c b/apps/main.c index 1e012efb3c..cf7d302fc9 100644 --- a/apps/main.c +++ b/apps/main.c @@ -77,6 +77,7 @@ #include "statusbar-skinned.h" #include "bootchart.h" #include "logdiskf.h" +#include "bootdata.h" #if (CONFIG_PLATFORM & PLATFORM_ANDROID) #include "notification.h" #endif @@ -445,6 +446,10 @@ static void init(void) core_allocator_init(); kernel_init(); +#if defined(HAVE_BOOTDATA) && !defined(BOOTLOADER) + verify_boot_data(); +#endif + /* early early early! */ filesystem_init(); diff --git a/firmware/SOURCES b/firmware/SOURCES index f6d35fb5ea..87cadfd55f 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -57,6 +57,9 @@ target/hosted/rolo.c #if defined(HAVE_BOOTDATA) || defined(HAVE_MULTIBOOT) common/multiboot.c +#ifndef BOOTLOADER +common/bootdata.c +#endif #endif #ifdef HAVE_SDL diff --git a/firmware/common/bootdata.c b/firmware/common/bootdata.c new file mode 100644 index 0000000000..fa74c5fe91 --- /dev/null +++ b/firmware/common/bootdata.c @@ -0,0 +1,74 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2022 by Aidan MacDonald + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include "bootdata.h" +#include "crc32.h" +#include + +#ifdef BOOTLOADER +# error "not to be included in bootloader builds" +#endif + +bool boot_data_valid; + +static bool verify_boot_data_v0(void) INIT_ATTR; +static bool verify_boot_data_v0(void) +{ + /* validate protocol version */ + if (boot_data.version != 0) + return false; + + /* validate length */ + if (boot_data.length != 4) + return false; + + return true; +} + +struct verify_bd_entry +{ + int version; + bool (*verify) (void); +}; + +static const struct verify_bd_entry verify_bd[] INITDATA_ATTR = { + { 0, verify_boot_data_v0 }, +}; + +void verify_boot_data(void) +{ + /* verify payload with checksum - all protocol versions */ + uint32_t crc = crc_32(boot_data.payload, boot_data.length, 0xffffffff); + if (crc != boot_data.crc) + return; + + /* apply verification specific to the protocol version */ + for (size_t i = 0; i < ARRAYLEN(verify_bd); ++i) + { + const struct verify_bd_entry *e = &verify_bd[i]; + if (e->version == boot_data.version) + { + if (e->verify()) + boot_data_valid = true; + + return; + } + } +} diff --git a/firmware/common/multiboot.c b/firmware/common/multiboot.c index c2cedc102d..c292aa1c30 100644 --- a/firmware/common/multiboot.c +++ b/firmware/common/multiboot.c @@ -26,44 +26,57 @@ #include #include +static void write_bootdata_v0(struct boot_data_t *data, unsigned int boot_volume) +{ + memset(data->payload, data->length, 0); + + data->boot_volume = boot_volume; + data->version = 0; +} + /* Write bootdata into location in FIRMWARE marked by magic header * Assumes buffer is already loaded with the firmware image * We just need to find the location and write data into the * payload region along with the crc for later verification and use. * Returns payload len on success, - * On error returns EKEY_NOT_FOUND + * On error returns false */ -int write_bootdata(unsigned char* buf, int len, unsigned int boot_volume) +bool write_bootdata(unsigned char* buf, int len, unsigned int boot_volume) { - struct boot_data_t bl_boot_data; - struct boot_data_t *fw_boot_data = NULL; int search_len = MIN(len, BOOT_DATA_SEARCH_SIZE) - sizeof(struct boot_data_t); - int payload_len = EKEY_NOT_FOUND; /* search for boot data header prior to search_len */ for(int i = 0; i < search_len; i++) { - fw_boot_data = (struct boot_data_t*) &buf[i]; - if (fw_boot_data->magic[0] != BOOT_DATA_MAGIC0 || - fw_boot_data->magic[1] != BOOT_DATA_MAGIC1) + struct boot_data_t *data = (struct boot_data_t *)&buf[i]; + if (data->magic[0] != BOOT_DATA_MAGIC0 || + data->magic[1] != BOOT_DATA_MAGIC1) continue; - memset(&bl_boot_data.payload, 0, BOOT_DATA_PAYLOAD_SIZE); - bl_boot_data.boot_volume = boot_volume; + /* Ignore it if the length extends past the end of the buffer. */ + int data_len = offsetof(struct boot_data_t, payload) + data->length; + if (i + data_len > len) + continue; + + /* Determine the maximum supported boot protocol version. + * Version 0 firmware may use 0 or 0xff, all other versions + * declare the highest supported version in the version byte. */ + int proto_version = 0; + if (data->version < 0xff) + proto_version = MIN(BOOT_DATA_VERSION, data->version); - memset(fw_boot_data->payload, 0, fw_boot_data->length); - /* determine maximum bytes we can write to firmware - BOOT_DATA_PAYLOAD_SIZE is the size the bootloader expects */ - payload_len = MIN(BOOT_DATA_PAYLOAD_SIZE, fw_boot_data->length); - fw_boot_data->length = payload_len; - /* copy data to FIRMWARE bootdata struct */ - memcpy(fw_boot_data->payload, &bl_boot_data.payload, payload_len); - /* crc will be used within the firmware to check validity of bootdata */ - fw_boot_data->crc = crc_32(fw_boot_data->payload, payload_len, 0xffffffff); - break; + /* Write boot data according to the selected protocol */ + if (proto_version == 0) + write_bootdata_v0(data, boot_volume); + else + break; + /* Calculate payload CRC, used by all protocol versions. */ + data->crc = crc_32(data->payload, data->length, 0xffffffff); + return true; } - return payload_len; + + return false; } #ifdef HAVE_MULTIBOOT diff --git a/firmware/common/rb-loader.c b/firmware/common/rb-loader.c index 0256f21884..61d8b1ddd2 100644 --- a/firmware/common/rb-loader.c +++ b/firmware/common/rb-loader.c @@ -112,7 +112,7 @@ int load_firmware(unsigned char* buf, const char* firmware, int buffer_size) { ret = load_firmware_filename(buf, filename, buffer_size); /* if firmware has no boot_data don't load from external drive */ - if (write_bootdata(buf, ret, i) <= 0) + if (!write_bootdata(buf, ret, i)) ret = EKEY_NOT_FOUND; } /* if ret is valid breaks from loop to continue loading */ diff --git a/firmware/export/bootdata.h b/firmware/export/bootdata.h index 322d50c20d..319f442511 100644 --- a/firmware/export/bootdata.h +++ b/firmware/export/bootdata.h @@ -22,6 +22,7 @@ #ifndef __ASSEMBLER__ #include +#include "system.h" #endif /* /!\ This file can be included in assembly files /!\ */ @@ -36,6 +37,7 @@ #define BOOT_DATA_MAGIC0 ('r' | 'b' << 8 | 'm' << 16 | 'a' << 24) #define BOOT_DATA_MAGIC1 ('g' | 'i' << 8 | 'c' << 16 | '!' << 24) +#define BOOT_DATA_VERSION 0 /* maximum size of payload */ #define BOOT_DATA_PAYLOAD_SIZE 4 @@ -58,6 +60,7 @@ struct boot_data_t struct { uint8_t boot_volume; + uint8_t version; }; uint8_t payload[BOOT_DATA_PAYLOAD_SIZE]; }; @@ -65,6 +68,9 @@ struct boot_data_t #if !defined(BOOTLOADER) extern struct boot_data_t boot_data; +extern bool boot_data_valid; + +void verify_boot_data(void) INIT_ATTR; #endif #else /* __ASSEMBLER__ */ @@ -76,7 +82,9 @@ boot_data: .word BOOT_DATA_MAGIC0 .word BOOT_DATA_MAGIC1 .word BOOT_DATA_PAYLOAD_SIZE - .space BOOT_DATA_PAYLOAD_SIZE, 0xff /* payload, initialised with value 0xff */ + .byte 0xff /* boot volume */ + .byte BOOT_DATA_VERSION /* maximum supported boot protocol version */ + .space (BOOT_DATA_PAYLOAD_SIZE - 2), 0xff /* remainder of payload */ .endm #endif diff --git a/firmware/export/multiboot.h b/firmware/export/multiboot.h index 0132b8531f..4174e71d61 100644 --- a/firmware/export/multiboot.h +++ b/firmware/export/multiboot.h @@ -21,7 +21,7 @@ #ifndef __MULTIBOOT_H__ #define __MULTIBOOT_H__ -extern int write_bootdata(unsigned char* buf, int len, unsigned int boot_volume); +extern bool write_bootdata(unsigned char* buf, int len, unsigned int boot_volume); #ifdef HAVE_MULTIBOOT extern int get_redirect_dir(char* buf, int buffer_size, int volume, const char* rootdir, const char* firmware); diff --git a/firmware/include/dircache_redirect.h b/firmware/include/dircache_redirect.h index f51ce70690..d4d72978c0 100644 --- a/firmware/include/dircache_redirect.h +++ b/firmware/include/dircache_redirect.h @@ -29,7 +29,6 @@ #include "rb-loader.h" #include "multiboot.h" #include "bootdata.h" -#include "crc32.h" #endif #ifndef RB_ROOT_VOL_HIDDEN @@ -144,8 +143,7 @@ static inline void volume_onmount_internal(IF_MV_NONVOID(int volume)) char rtpath[MAX_PATH / 2]; make_volume_root(volume, path); - unsigned int crc = crc_32(boot_data.payload, boot_data.length, 0xffffffff); - if (crc > 0 && crc == boot_data.crc) + if (boot_data_valid) { /* we need to mount the drive before we can access it */ root_mount_path(path, 0); /* root could be different folder don't hide */ @@ -174,7 +172,7 @@ static inline void volume_onmount_internal(IF_MV_NONVOID(int volume)) root_mount_path(rtpath, NSITEM_CONTENTS); } - } /*CRC OK*/ + } else { standard_redirect: diff --git a/firmware/rolo.c b/firmware/rolo.c index a3e6d5c2b9..1b37b6f771 100644 --- a/firmware/rolo.c +++ b/firmware/rolo.c @@ -250,12 +250,9 @@ int rolo_load(const char* filename) err = LOAD_FIRMWARE(filebuf, filename, filebuf_size); #if defined(HAVE_BOOTDATA) && !defined(SIMULATOR) - /* write the bootdata as if rolo were the bootloader */ - unsigned int crc = 0; - if (strcmp(filename, BOOTDIR "/" BOOTFILE) == 0) - crc = crc_32(boot_data.payload, boot_data.length, 0xffffffff); - - if(crc > 0 && crc == boot_data.crc) + /* write the bootdata as if rolo were the bootloader + * FIXME: this won't work for root redirect... */ + if (!strcmp(filename, BOOTDIR "/" BOOTFILE) && boot_data_valid) write_bootdata(filebuf, filebuf_size, boot_data.boot_volume); /* rb-loader.c */ #endif diff --git a/firmware/target/arm/pp/mi4-loader.c b/firmware/target/arm/pp/mi4-loader.c index f609e3ff7a..14bb5e6f47 100644 --- a/firmware/target/arm/pp/mi4-loader.c +++ b/firmware/target/arm/pp/mi4-loader.c @@ -256,7 +256,7 @@ int load_mi4(unsigned char* buf, const char* firmware, unsigned int buffer_size) { ret = load_mi4_filename(buf, filename, buffer_size); /* if firmware has no boot_data don't load from external drive */ - if (write_bootdata(buf, ret, i) <= 0) + if (!write_bootdata(buf, ret, i)) ret = EKEY_NOT_FOUND; } /* if ret is valid breaks from loop to continue loading */ -- cgit v1.2.3