From ea52a19c001c0e9963a046588d6bae82bb329843 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Wed, 9 Jan 2019 20:06:01 +0800 Subject: [PATCH] build system: include SHA256 hash of ELF file into app_desc structure --- components/app_update/esp_app_desc.c | 23 +++++++++ components/app_update/include/esp_ota_ops.h | 10 ++++ components/app_update/test/test_app_desc.c | 50 +++++++++++++++++++ components/esp32/cpu_start.c | 6 ++- components/esptool_py/Makefile.projbuild | 2 + components/esptool_py/project_include.cmake | 4 +- examples/get-started/blink/example_test.py | 55 +++++++++++++++++++++ 7 files changed, 147 insertions(+), 3 deletions(-) create mode 100644 components/app_update/test/test_app_desc.c create mode 100644 examples/get-started/blink/example_test.py diff --git a/components/app_update/esp_app_desc.c b/components/app_update/esp_app_desc.c index 20c487c1c..23d3b4cc7 100644 --- a/components/app_update/esp_app_desc.c +++ b/components/app_update/esp_app_desc.c @@ -13,7 +13,9 @@ // limitations under the License. #include +#include #include "esp_ota_ops.h" +#include "esp_attr.h" #include "sdkconfig.h" // Application version info @@ -60,3 +62,24 @@ const esp_app_desc_t *esp_ota_get_app_description(void) { return &esp_app_desc; } + +/* The following two functions may be called from the panic handler + * or core dump, hence IRAM_ATTR. + */ + +static inline char IRAM_ATTR to_hex_digit(unsigned val) +{ + return (val < 10) ? ('0' + val) : ('a' + val - 10); +} + +int IRAM_ATTR esp_ota_get_app_elf_sha256(char* dst, size_t size) +{ + size_t n = MIN((size - 1) / 2, sizeof(esp_app_desc.app_elf_sha256)); + const uint8_t* src = esp_app_desc.app_elf_sha256; + for (size_t i = 0; i < n; ++i) { + dst[2*i] = to_hex_digit(src[i] >> 4); + dst[2*i + 1] = to_hex_digit(src[i] & 0xf); + } + dst[2*n] = 0; + return 2*n + 1; +} diff --git a/components/app_update/include/esp_ota_ops.h b/components/app_update/include/esp_ota_ops.h index 94a34b4f9..635144638 100644 --- a/components/app_update/include/esp_ota_ops.h +++ b/components/app_update/include/esp_ota_ops.h @@ -55,6 +55,16 @@ typedef uint32_t esp_ota_handle_t; */ const esp_app_desc_t *esp_ota_get_app_description(void); +/** + * @brief Fill the provided buffer with SHA256 of the ELF file, formatted as hexadecimal, null-terminated. + * If the buffer size is not sufficient to fit the entire SHA256 in hex plus a null terminator, + * the largest possible number of bytes will be written followed by a null. + * @param dst Destination buffer + * @param size Size of the buffer + * @return Number of bytes written to dst (including null terminator) + */ +int esp_ota_get_app_elf_sha256(char* dst, size_t size); + /** * @brief Commence an OTA update writing to the specified partition. diff --git a/components/app_update/test/test_app_desc.c b/components/app_update/test/test_app_desc.c new file mode 100644 index 000000000..318729432 --- /dev/null +++ b/components/app_update/test/test_app_desc.c @@ -0,0 +1,50 @@ +#include +#include "esp_ota_ops.h" +#include "unity.h" + +TEST_CASE("esp_ota_get_app_elf_sha256 test", "[esp_app_desc]") +{ + const int sha256_hex_len = 64; + char dst[sha256_hex_len + 2]; + const char fill = 0xcc; + int res; + size_t len; + + char ref_sha256[sha256_hex_len + 1]; + const esp_app_desc_t* desc = esp_ota_get_app_description(); + for (int i = 0; i < sizeof(ref_sha256) / 2; ++i) { + snprintf(ref_sha256 + 2*i, 3, "%02x", desc->app_elf_sha256[i]); + } + ref_sha256[sha256_hex_len] = 0; + + printf("Ref: %s\n", ref_sha256); + + memset(dst, fill, sizeof(dst)); + len = sizeof(dst); + res = esp_ota_get_app_elf_sha256(dst, len); + printf("%d: %s (%d)\n", len, dst, res); + TEST_ASSERT_EQUAL(sha256_hex_len + 1, res); + TEST_ASSERT_EQUAL(0, memcmp(dst, ref_sha256, res - 1)); + TEST_ASSERT_EQUAL_HEX(0, dst[sha256_hex_len]); + TEST_ASSERT_EQUAL_HEX(fill, dst[sha256_hex_len + 1]); + + memset(dst, fill, sizeof(dst)); + len = 9; + res = esp_ota_get_app_elf_sha256(dst, len); + printf("%d: %s (%d)\n", len, dst, res); + TEST_ASSERT_EQUAL(9, res); + TEST_ASSERT_EQUAL(0, memcmp(dst, ref_sha256, res - 1)); + TEST_ASSERT_EQUAL_HEX(0, dst[8]); + TEST_ASSERT_EQUAL_HEX(fill, dst[9]); + + memset(dst, fill, sizeof(dst)); + len = 8; + res = esp_ota_get_app_elf_sha256(dst, len); + printf("%d: %s (%d)\n", len, dst, res); + // should output even number of characters plus '\0' + TEST_ASSERT_EQUAL(7, res); + TEST_ASSERT_EQUAL(0, memcmp(dst, ref_sha256, res - 1)); + TEST_ASSERT_EQUAL_HEX(0, dst[6]); + TEST_ASSERT_EQUAL_HEX(fill, dst[7]); + TEST_ASSERT_EQUAL_HEX(fill, dst[8]); +} diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index d2f7a6317..bf28857fb 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -189,9 +189,11 @@ void IRAM_ATTR call_start_cpu0() ESP_EARLY_LOGI(TAG, "Secure version: %d", app_desc->secure_version); #endif #ifdef CONFIG_APP_COMPILE_TIME_DATE - ESP_EARLY_LOGI(TAG, "Compile time: %s", app_desc->time); - ESP_EARLY_LOGI(TAG, "Compile date: %s", app_desc->date); + ESP_EARLY_LOGI(TAG, "Compile time: %s %s", app_desc->date, app_desc->time); #endif + char buf[17]; + esp_ota_get_app_elf_sha256(buf, sizeof(buf)); + ESP_EARLY_LOGI(TAG, "ELF file SHA256: %s...", buf); ESP_EARLY_LOGI(TAG, "ESP-IDF: %s", app_desc->idf_ver); } diff --git a/components/esptool_py/Makefile.projbuild b/components/esptool_py/Makefile.projbuild index c3ce445dd..25f2487ab 100644 --- a/components/esptool_py/Makefile.projbuild +++ b/components/esptool_py/Makefile.projbuild @@ -39,6 +39,8 @@ endif endif endif +ESPTOOL_ELF2IMAGE_OPTIONS += --elf-sha256-offset 0xb0 + ESPTOOLPY_WRITE_FLASH=$(ESPTOOLPY_SERIAL) write_flash $(if $(CONFIG_ESPTOOLPY_COMPRESSED),-z,-u) $(ESPTOOL_WRITE_FLASH_OPTIONS) ESPTOOL_ALL_FLASH_ARGS += $(APP_OFFSET) $(APP_BIN) diff --git a/components/esptool_py/project_include.cmake b/components/esptool_py/project_include.cmake index 83e129ae6..ff6da5b8a 100644 --- a/components/esptool_py/project_include.cmake +++ b/components/esptool_py/project_include.cmake @@ -52,6 +52,8 @@ if(CONFIG_SECURE_BOOT_ENABLED AND ${ESPTOOLPY_ELF2IMAGE_FLASH_OPTIONS} --secure-pad) endif() +set(ESPTOOLPY_ELF2IMAGE_OPTIONS --elf-sha256-offset 0xb0) + if(CONFIG_ESPTOOLPY_FLASHSIZE_DETECT) # Set ESPFLASHSIZE to 'detect' *after* elf2image options are generated, # as elf2image can't have 'detect' as an option... @@ -75,7 +77,7 @@ endif() # Add 'app.bin' target - generates with elf2image # add_custom_command(OUTPUT "${IDF_BUILD_ARTIFACTS_DIR}/${unsigned_project_binary}" - COMMAND ${ESPTOOLPY} elf2image ${ESPTOOLPY_ELF2IMAGE_FLASH_OPTIONS} + COMMAND ${ESPTOOLPY} elf2image ${ESPTOOLPY_ELF2IMAGE_FLASH_OPTIONS} ${ESPTOOLPY_ELF2IMAGE_OPTIONS} -o "${IDF_BUILD_ARTIFACTS_DIR}/${unsigned_project_binary}" "${IDF_PROJECT_EXECUTABLE}" DEPENDS ${IDF_PROJECT_EXECUTABLE} VERBATIM diff --git a/examples/get-started/blink/example_test.py b/examples/get-started/blink/example_test.py new file mode 100644 index 000000000..91b0f9bc0 --- /dev/null +++ b/examples/get-started/blink/example_test.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python + +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals +import re +import os +import sys +import hashlib + +try: + import IDF +except ImportError: + # This environment variable is expected on the host machine + test_fw_path = os.getenv("TEST_FW_PATH") + if test_fw_path and test_fw_path not in sys.path: + sys.path.insert(0, test_fw_path) + + import IDF + +import Utility + + +def verify_elf_sha256_embedding(dut): + elf_file = os.path.join(dut.app.binary_path, "blink.elf") + sha256 = hashlib.sha256() + with open(elf_file, "rb") as f: + sha256.update(f.read()) + sha256_expected = sha256.hexdigest() + + dut.reset() + sha256_reported = dut.expect(re.compile(r'ELF file SHA256:\s+([a-f0-9]+)'), timeout=5)[0] + + Utility.console_log('ELF file SHA256: %s' % sha256_expected) + Utility.console_log('ELF file SHA256 (reported by the app): %s' % sha256_reported) + # the app reports only the first several hex characters of the SHA256, check that they match + if not sha256_expected.startswith(sha256_reported): + raise ValueError('ELF file SHA256 mismatch') + + +@IDF.idf_example_test(env_tag="Example_WIFI") +def test_examples_blink(env, extra_data): + dut = env.get_dut("blink", "examples/get-started/blink") + binary_file = os.path.join(dut.app.binary_path, "blink.bin") + bin_size = os.path.getsize(binary_file) + IDF.log_performance("blink_bin_size", "{}KB".format(bin_size // 1024)) + IDF.check_performance("blink_bin_size", bin_size // 1024) + + dut.start_app() + + verify_elf_sha256_embedding(dut) + + +if __name__ == '__main__': + test_examples_blink()