diff --git a/.gitlab/ci/target-test.yml b/.gitlab/ci/target-test.yml index d0e33adc65..c599421399 100644 --- a/.gitlab/ci/target-test.yml +++ b/.gitlab/ci/target-test.yml @@ -345,7 +345,7 @@ test_app_test_005: test_app_test_esp32_generic: extends: .test_app_esp32_template - parallel: 4 + parallel: 5 tags: - ESP32 - Example_GENERIC diff --git a/components/esp_netif/test_apps/CMakeLists.txt b/components/esp_netif/test_apps/CMakeLists.txt index 03a8cad939..dc191a89b8 100644 --- a/components/esp_netif/test_apps/CMakeLists.txt +++ b/components/esp_netif/test_apps/CMakeLists.txt @@ -5,3 +5,6 @@ set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/unit-test-app/components") include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(esp_netif_test) + +idf_component_get_property(lib esp_netif COMPONENT_LIB) +target_compile_options(${lib} PRIVATE "-fsanitize=undefined" "-fno-sanitize=shift-base") diff --git a/components/esp_system/CMakeLists.txt b/components/esp_system/CMakeLists.txt index ce9950d498..25e55dfd0c 100644 --- a/components/esp_system/CMakeLists.txt +++ b/components/esp_system/CMakeLists.txt @@ -19,7 +19,8 @@ else() "startup.c" "system_time.c" "stack_check.c" - "task_wdt.c") + "task_wdt.c" + "ubsan.c") if(NOT (${target} STREQUAL "esp32c3") ) list(APPEND srcs "dbg_stubs.c") @@ -58,3 +59,7 @@ if(CONFIG_IDF_ENV_FPGA) # Forces the linker to include fpga stubs from this component target_link_libraries(${COMPONENT_LIB} INTERFACE "-u esp_common_include_fpga_overrides") endif() + +# Force linking UBSAN hooks. If UBSAN is not enabled, the hooks will ultimately be removed +# due to -ffunction-sections -Wl,--gc-sections options. +target_link_libraries(${COMPONENT_LIB} INTERFACE "-u __ubsan_include") diff --git a/components/esp_system/component.mk b/components/esp_system/component.mk index 881d42b72d..0a3cfa5984 100644 --- a/components/esp_system/component.mk +++ b/components/esp_system/component.mk @@ -19,6 +19,10 @@ ifndef CONFIG_IDF_ENV_FPGA COMPONENT_OBJEXCLUDE += fpga_overrides.o endif +# Force linking UBSAN hooks. If UBSAN is not enabled, the hooks will ultimately be removed +# due to -ffunction-sections -Wl,--gc-sections options. +COMPONENT_ADD_LDFLAGS += -u __ubsan_include + include $(COMPONENT_PATH)/port/soc/$(SOC_NAME)/component.mk # disable stack protection in files which are involved in initialization of that feature diff --git a/components/esp_system/linker.lf b/components/esp_system/linker.lf index a458ccbadc..b7639db8da 100644 --- a/components/esp_system/linker.lf +++ b/components/esp_system/linker.lf @@ -8,6 +8,7 @@ entries: esp_err (noflash) esp_system:esp_system_abort (noflash) + ubsan (noflash) if ESP_CONSOLE_USB_CDC_SUPPORT_ETS_PRINTF: usb_console:esp_usb_console_write_char (noflash) diff --git a/components/esp_system/panic.c b/components/esp_system/panic.c index d35057ccb1..1ed9cc524f 100644 --- a/components/esp_system/panic.c +++ b/components/esp_system/panic.c @@ -348,7 +348,7 @@ void esp_panic_handler(panic_info_t *info) } -void __attribute__((noreturn)) panic_abort(const char *details) +void __attribute__((noreturn,no_sanitize_undefined)) panic_abort(const char *details) { g_panic_abort = true; s_panic_abort_details = (char*) details; diff --git a/components/esp_system/ubsan.c b/components/esp_system/ubsan.c new file mode 100644 index 0000000000..8f5e123cc7 --- /dev/null +++ b/components/esp_system/ubsan.c @@ -0,0 +1,294 @@ +// Copyright (c) 2016, Linaro Limited +// Modified for HelenOS use by Jiří Zárevúcky. +// Adaptations for ESP-IDF Copyright (c) 2020 Espressif Systems (Shanghai) Co. Ltd. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include +#include +#include +#include "esp_system.h" +#include "esp_rom_sys.h" +#include "hal/cpu_hal.h" + + +struct source_location { + const char *file_name; + uint32_t line; + uint32_t column; +}; + +struct type_descriptor { + uint16_t type_kind; + uint16_t type_info; + char type_name[]; +}; + +struct type_mismatch_data { + struct source_location loc; + struct type_descriptor *type; + unsigned long alignment; + unsigned char type_check_kind; +}; + +struct type_mismatch_data_v1 { + struct source_location loc; + struct type_descriptor *type; + unsigned char log_alignment; + unsigned char type_check_kind; +}; + +struct overflow_data { + struct source_location loc; + struct type_descriptor *type; +}; + +struct shift_out_of_bounds_data { + struct source_location loc; + struct type_descriptor *lhs_type; + struct type_descriptor *rhs_type; +}; + +struct out_of_bounds_data { + struct source_location loc; + struct type_descriptor *array_type; + struct type_descriptor *index_type; +}; + +struct unreachable_data { + struct source_location loc; +}; + +struct vla_bound_data { + struct source_location loc; + struct type_descriptor *type; +}; + +struct invalid_value_data { + struct source_location loc; + struct type_descriptor *type; +}; + +struct nonnull_arg_data { + struct source_location loc; +}; + +struct nonnull_return_data { + struct source_location loc; + struct source_location attr_loc; +}; + +struct pointer_overflow_data { + struct source_location loc; +}; + +struct invalid_builtin_data { + struct source_location loc; + unsigned char kind; +}; + + +static void __ubsan_default_handler(struct source_location *loc, const char *func) __attribute__((noreturn)); + +/* + * When compiling with -fsanitize=undefined the compiler expects functions + * with the following signatures. The functions are never called directly, + * only when undefined behavior is detected in instrumented code. + */ +void __ubsan_handle_type_mismatch(struct type_mismatch_data *data, unsigned long ptr); +void __ubsan_handle_type_mismatch_v1(struct type_mismatch_data_v1 *data, unsigned long ptr); +void __ubsan_handle_add_overflow(struct overflow_data *data, unsigned long lhs, unsigned long rhs); +void __ubsan_handle_sub_overflow(struct overflow_data *data, unsigned long lhs, unsigned long rhs); +void __ubsan_handle_mul_overflow(struct overflow_data *data, unsigned long lhs, unsigned long rhs); +void __ubsan_handle_negate_overflow(struct overflow_data *data, unsigned long old_val); +void __ubsan_handle_divrem_overflow(struct overflow_data *data, unsigned long lhs, unsigned long rhs); +void __ubsan_handle_shift_out_of_bounds(struct shift_out_of_bounds_data *data, unsigned long lhs, unsigned long rhs); +void __ubsan_handle_out_of_bounds(struct out_of_bounds_data *data, unsigned long idx); +void __ubsan_handle_missing_return(struct unreachable_data *data); +void __ubsan_handle_vla_bound_not_positive(struct vla_bound_data *data, unsigned long bound); +void __ubsan_handle_load_invalid_value(struct invalid_value_data *data, unsigned long val); +void __ubsan_handle_nonnull_arg(struct nonnull_arg_data *data); +void __ubsan_handle_nonnull_return(struct nonnull_return_data *data); +void __ubsan_handle_builtin_unreachable(struct unreachable_data *data); +void __ubsan_handle_pointer_overflow(struct pointer_overflow_data *data, + unsigned long base, unsigned long result); + +static void __ubsan_maybe_debugbreak(void) +{ + if (cpu_hal_is_debugger_attached()) { + cpu_hal_break(); + } +} + +static void __ubsan_default_handler(struct source_location *loc, const char *func) +{ + /* Although the source location is available here, it is not printed: + * + * - We could use "snprintf", but that uses a lot of stack, and may allocate memory, + * so is not safe from UBSAN handler. + * - Alternatively, "itoa" could be used. However itoa doesn't take the remaining + * string length as as argument and is therefore unsafe (nor does it return + * the number of characters written). itoa is also not present in ESP32-S2 ROM, + * and would need to be placed into IRAM on that chip. + * - Third option is to print the message using esp_rom_printf, and not pass anything + * to esp_system_abort. However we'd like to capture this information, e.g. for the + * purpose of including the abort reason into core dumps. + * + * Since the source file and line number are already printed while decoding + * the panic backtrace, not printing the line number here seems to be an okay choice. + */ + char msg[60] = {}; + (void) strlcat(msg, "Undefined behavior of type ", sizeof(msg)); + (void) strlcat(msg, func + strlen("__ubsan_handle_"), sizeof(msg)); + esp_system_abort(msg); +} + +void __ubsan_handle_type_mismatch(struct type_mismatch_data *data, + unsigned long ptr) +{ + __ubsan_maybe_debugbreak(); + __ubsan_default_handler(&data->loc, __func__); +} + +void __ubsan_handle_type_mismatch_v1(struct type_mismatch_data_v1 *data, + unsigned long ptr) +{ + __ubsan_maybe_debugbreak(); + __ubsan_default_handler(&data->loc, __func__); +} + +void __ubsan_handle_add_overflow(struct overflow_data *data, + unsigned long lhs, + unsigned long rhs) +{ + __ubsan_maybe_debugbreak(); + __ubsan_default_handler(&data->loc, __func__); +} + +void __ubsan_handle_sub_overflow(struct overflow_data *data, + unsigned long lhs, + unsigned long rhs) +{ + __ubsan_maybe_debugbreak(); + __ubsan_default_handler(&data->loc, __func__); +} + +void __ubsan_handle_mul_overflow(struct overflow_data *data, + unsigned long lhs, + unsigned long rhs) +{ + __ubsan_maybe_debugbreak(); + __ubsan_default_handler(&data->loc, __func__); +} + +void __ubsan_handle_negate_overflow(struct overflow_data *data, + unsigned long old_val) +{ + __ubsan_maybe_debugbreak(); + __ubsan_default_handler(&data->loc, __func__); +} + +void __ubsan_handle_divrem_overflow(struct overflow_data *data, + unsigned long lhs, + unsigned long rhs) +{ + __ubsan_maybe_debugbreak(); + __ubsan_default_handler(&data->loc, __func__); +} + +void __ubsan_handle_shift_out_of_bounds(struct shift_out_of_bounds_data *data, + unsigned long lhs, + unsigned long rhs) +{ + if (rhs == 32) { + return; + } + __ubsan_maybe_debugbreak(); + __ubsan_default_handler(&data->loc, __func__); +} + +void __ubsan_handle_out_of_bounds(struct out_of_bounds_data *data, + unsigned long idx) +{ + __ubsan_maybe_debugbreak(); + __ubsan_default_handler(&data->loc, __func__); +} + +void __ubsan_handle_missing_return(struct unreachable_data *data) +{ + __ubsan_maybe_debugbreak(); + __ubsan_default_handler(&data->loc, __func__); +} + +void __ubsan_handle_vla_bound_not_positive(struct vla_bound_data *data, + unsigned long bound) +{ + __ubsan_maybe_debugbreak(); + __ubsan_default_handler(&data->loc, __func__); +} + +void __ubsan_handle_load_invalid_value(struct invalid_value_data *data, + unsigned long val) +{ + __ubsan_maybe_debugbreak(); + __ubsan_default_handler(&data->loc, __func__); +} + +void __ubsan_handle_nonnull_arg(struct nonnull_arg_data *data) +{ + __ubsan_maybe_debugbreak(); + __ubsan_default_handler(&data->loc, __func__); +} + +void __ubsan_handle_nonnull_return(struct nonnull_return_data *data) +{ + __ubsan_maybe_debugbreak(); + __ubsan_default_handler(&data->loc, __func__); +} + +void __ubsan_handle_builtin_unreachable(struct unreachable_data *data) +{ + __ubsan_maybe_debugbreak(); + __ubsan_default_handler(&data->loc, __func__); +} + +void __ubsan_handle_pointer_overflow(struct pointer_overflow_data *data, + unsigned long base, unsigned long result) +{ + __ubsan_maybe_debugbreak(); + __ubsan_default_handler(&data->loc, __func__); +} + +void __ubsan_handle_invalid_builtin(struct invalid_builtin_data *data) +{ + __ubsan_maybe_debugbreak(); + __ubsan_default_handler(&data->loc, __func__); +} + +/* Hook for the linker to include this object file */ +void __ubsan_include(void) +{ +} diff --git a/components/hal/esp32/include/hal/cpu_ll.h b/components/hal/esp32/include/hal/cpu_ll.h index 7a83ddbc43..49d2e3947d 100644 --- a/components/hal/esp32/include/hal/cpu_ll.h +++ b/components/hal/esp32/include/hal/cpu_ll.h @@ -170,7 +170,7 @@ static inline bool cpu_ll_is_debugger_attached(void) static inline void cpu_ll_break(void) { - __asm__ ("break 0,0"); + __asm__ ("break 1,15"); } static inline void cpu_ll_set_vecbase(const void* vecbase) diff --git a/components/hal/esp32/include/hal/mpu_ll.h b/components/hal/esp32/include/hal/mpu_ll.h index fc491e2b8f..2ec955b48f 100644 --- a/components/hal/esp32/include/hal/mpu_ll.h +++ b/components/hal/esp32/include/hal/mpu_ll.h @@ -22,7 +22,7 @@ extern "C" { #endif -static inline uint32_t mpu_ll_id_to_addr(int id) +static inline uint32_t mpu_ll_id_to_addr(unsigned id) { // vpn - id // 0x00000000 = 0 diff --git a/components/hal/esp32c3/include/hal/mpu_ll.h b/components/hal/esp32c3/include/hal/mpu_ll.h index cfeec1d6a6..9e07ee3e4b 100644 --- a/components/hal/esp32c3/include/hal/mpu_ll.h +++ b/components/hal/esp32c3/include/hal/mpu_ll.h @@ -22,7 +22,7 @@ extern "C" { /* This LL is currently unused for ESP32-C3 - cleanup is TODO ESP32-C3 IDF-2375 */ -static inline uint32_t mpu_ll_id_to_addr(int id) +static inline uint32_t mpu_ll_id_to_addr(unsigned id) { abort(); } diff --git a/components/hal/esp32s2/include/hal/cpu_ll.h b/components/hal/esp32s2/include/hal/cpu_ll.h index c70955eba2..ded1ca3ab4 100644 --- a/components/hal/esp32s2/include/hal/cpu_ll.h +++ b/components/hal/esp32s2/include/hal/cpu_ll.h @@ -165,7 +165,7 @@ static inline bool cpu_ll_is_debugger_attached(void) static inline void cpu_ll_break(void) { - __asm__ ("break 0,0"); + __asm__ ("break 1,15"); } static inline void cpu_ll_set_vecbase(const void* vecbase) diff --git a/components/hal/esp32s2/include/hal/mpu_ll.h b/components/hal/esp32s2/include/hal/mpu_ll.h index fc491e2b8f..2ec955b48f 100644 --- a/components/hal/esp32s2/include/hal/mpu_ll.h +++ b/components/hal/esp32s2/include/hal/mpu_ll.h @@ -22,7 +22,7 @@ extern "C" { #endif -static inline uint32_t mpu_ll_id_to_addr(int id) +static inline uint32_t mpu_ll_id_to_addr(unsigned id) { // vpn - id // 0x00000000 = 0 diff --git a/components/hal/esp32s3/include/hal/cpu_ll.h b/components/hal/esp32s3/include/hal/cpu_ll.h index ea50870f93..cbcb8aa874 100644 --- a/components/hal/esp32s3/include/hal/cpu_ll.h +++ b/components/hal/esp32s3/include/hal/cpu_ll.h @@ -169,7 +169,7 @@ static inline bool cpu_ll_is_debugger_attached(void) static inline void cpu_ll_break(void) { - __asm__ ("break 0,0"); + __asm__ ("break 1,15"); } static inline void cpu_ll_set_vecbase(const void *vecbase) diff --git a/components/hal/esp32s3/include/hal/mpu_ll.h b/components/hal/esp32s3/include/hal/mpu_ll.h index fc491e2b8f..2ec955b48f 100644 --- a/components/hal/esp32s3/include/hal/mpu_ll.h +++ b/components/hal/esp32s3/include/hal/mpu_ll.h @@ -22,7 +22,7 @@ extern "C" { #endif -static inline uint32_t mpu_ll_id_to_addr(int id) +static inline uint32_t mpu_ll_id_to_addr(unsigned id) { // vpn - id // 0x00000000 = 0 diff --git a/components/soc/esp32/include/soc/soc_caps.h b/components/soc/esp32/include/soc/soc_caps.h index 8f2a29c3c9..b8c66aa3cf 100644 --- a/components/soc/esp32/include/soc/soc_caps.h +++ b/components/soc/esp32/include/soc/soc_caps.h @@ -165,7 +165,7 @@ /*-------------------------- MPU CAPS ----------------------------------------*/ //TODO: correct the caller and remove unsupported lines #define SOC_MPU_CONFIGURABLE_REGIONS_SUPPORTED 0 -#define SOC_MPU_MIN_REGION_SIZE 0x20000000 +#define SOC_MPU_MIN_REGION_SIZE 0x20000000U #define SOC_MPU_REGIONS_MAX_NUM 8 #define SOC_MPU_REGION_RO_SUPPORTED 0 #define SOC_MPU_REGION_WO_SUPPORTED 0 diff --git a/components/soc/esp32c3/include/soc/mpu_caps.h b/components/soc/esp32c3/include/soc/mpu_caps.h index b267547590..1d23e37507 100644 --- a/components/soc/esp32c3/include/soc/mpu_caps.h +++ b/components/soc/esp32c3/include/soc/mpu_caps.h @@ -15,7 +15,7 @@ #pragma once #define SOC_MPU_CONFIGURABLE_REGIONS_SUPPORTED 0 -#define SOC_MPU_MIN_REGION_SIZE 0x20000000 +#define SOC_MPU_MIN_REGION_SIZE 0x20000000U #define SOC_MPU_REGIONS_MAX_NUM 8 #define SOC_MPU_REGION_RO_SUPPORTED 0 #define SOC_MPU_REGION_WO_SUPPORTED 0 diff --git a/components/soc/esp32s2/include/soc/soc_caps.h b/components/soc/esp32s2/include/soc/soc_caps.h index 355994d420..fd4c788d5f 100644 --- a/components/soc/esp32s2/include/soc/soc_caps.h +++ b/components/soc/esp32s2/include/soc/soc_caps.h @@ -149,7 +149,7 @@ /*-------------------------- MPU CAPS ----------------------------------------*/ //TODO: correct the caller and remove unsupported lines #define SOC_MPU_CONFIGURABLE_REGIONS_SUPPORTED 0 -#define SOC_MPU_MIN_REGION_SIZE 0x20000000 +#define SOC_MPU_MIN_REGION_SIZE 0x20000000U #define SOC_MPU_REGIONS_MAX_NUM 8 #define SOC_MPU_REGION_RO_SUPPORTED 0 #define SOC_MPU_REGION_WO_SUPPORTED 0 diff --git a/components/soc/esp32s3/include/soc/mpu_caps.h b/components/soc/esp32s3/include/soc/mpu_caps.h index b267547590..1d23e37507 100644 --- a/components/soc/esp32s3/include/soc/mpu_caps.h +++ b/components/soc/esp32s3/include/soc/mpu_caps.h @@ -15,7 +15,7 @@ #pragma once #define SOC_MPU_CONFIGURABLE_REGIONS_SUPPORTED 0 -#define SOC_MPU_MIN_REGION_SIZE 0x20000000 +#define SOC_MPU_MIN_REGION_SIZE 0x20000000U #define SOC_MPU_REGIONS_MAX_NUM 8 #define SOC_MPU_REGION_RO_SUPPORTED 0 #define SOC_MPU_REGION_WO_SUPPORTED 0 diff --git a/docs/en/COPYRIGHT.rst b/docs/en/COPYRIGHT.rst index d0d8a26b18..ba154f6601 100644 --- a/docs/en/COPYRIGHT.rst +++ b/docs/en/COPYRIGHT.rst @@ -71,6 +71,8 @@ These third party libraries can be included into the application (firmware) prod * :component:`openthread`, Copyright (c) The OpenThread Authors, is licensed under Apache License 2.0 as described in :component_file:`LICENSE file`. +* :component_file:` UBSAN runtime ` — Copyright (c) 2016, Linaro Limited and Jiří Zárevúcky, licensed under the BSD 2-clause license. + Build Tools ----------- diff --git a/docs/en/api-guides/fatal-errors.rst b/docs/en/api-guides/fatal-errors.rst index b1d3c1e09e..591db1ab7c 100644 --- a/docs/en/api-guides/fatal-errors.rst +++ b/docs/en/api-guides/fatal-errors.rst @@ -17,6 +17,7 @@ In certain situations, execution of the program can not be continued in a well d - Stack overflow - Stack smashing protection check - Heap integrity check + - Undefined behavior sanitizer (UBSAN) checks - Failed assertions, via ``assert``, ``configASSERT`` and similar macros. @@ -389,3 +390,98 @@ The backtrace should point to the function where stack smashing has occured. Che .. |CPU_EXCEPTIONS_LIST| replace:: Illegal Instruction, Load/Store Alignment Error, Load/Store Prohibited error. .. |ILLEGAL_INSTR_MSG| replace:: Illegal instruction .. |CACHE_ERR_MSG| replace:: Cache error + +Undefined behavior sanitizer (UBSAN) checks +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Undefined behavior sanitizer (UBSAN) is a compiler feature which adds run-time checks for potentially incorrect operations, such as: + +- overflows (multiplication overflow, signed integer overflow) +- shift base or exponent errors (e.g. shift by more than 32 bits) +- integer conversion errors + +See `GCC documentation `_ of ``-fsanitize=undefined`` option for the complete list of supported checks. + +Enabling UBSAN +"""""""""""""" + +UBSAN is disabled by default. It can be enabled at file, component, or project level by adding ``-fsanitize=undefined`` compiler option in the build system. + +When enabling UBSAN for the code which uses hardware register header files (``soc/xxx_reg.h``), it is recommended to disable shift-base sanitizer using ``-fno-sanitize=shift-base`` option. This is due to the fact that ESP-IDF register header files currently contain patterns which cause false positives for this specific sanitizer option. + +To enable UBSAN at project level, add the following at the end of the project CMakeLists.txt file:: + + idf_build_set_property(COMPILE_OPTIONS "-fsanitize=undefined" "-fno-sanitize=shift-base" APPEND) + +Alternatively, pass these options through ``EXTRA_CFLAGS`` and ``EXTRA_CXXFLAGS`` environment variables. + +Enabling UBSAN results in significant increase of code and data size. Most applications, except for the trivial ones, will not fit into the available RAM of the microcontroller when UBSAN is enabled for the whole application. Therefore it is recommended that UBSAN is instead enabled for specific components under test. + +To enable UBSAN for the specific component (``component_name``) from the project CMakeLists.txt file, add the following at the end of the file:: + + idf_component_get_property(lib component_name COMPONENT_LIB) + target_compile_options(${lib} PRIVATE "-fsanitize=undefined" "-fno-sanitize=shift-base") + +.. note:: See the build system documentation for more information about :ref:`build properties` and :ref:`component properties`. + +To enable UBSAN for the specific component (``component_name``) from CMakeLists.txt of the same component, add the following at the end of the file:: + + target_compile_options(${COMPONENT_LIB} PRIVATE "-fsanitize=undefined" "-fno-sanitize=shift-base") + +UBSAN output +"""""""""""" + +When UBSAN detects an error, a message and the backtrace are printed, for example:: + + Undefined behavior of type out_of_bounds + + Backtrace:0x4008b383:0x3ffcd8b0 0x4008c791:0x3ffcd8d0 0x4008c587:0x3ffcd8f0 0x4008c6be:0x3ffcd950 0x400db74f:0x3ffcd970 0x400db99c:0x3ffcd9a0 + +When using :doc:`IDF Monitor `, the backtrace will be decoded to function names and source code locations, pointing to the location where the issue has happened (here it is ``main.c:128``):: + + 0x4008b383: panic_abort at /path/to/esp-idf/components/esp_system/panic.c:367 + + 0x4008c791: esp_system_abort at /path/to/esp-idf/components/esp_system/system_api.c:106 + + 0x4008c587: __ubsan_default_handler at /path/to/esp-idf/components/esp_system/ubsan.c:152 + + 0x4008c6be: __ubsan_handle_out_of_bounds at /path/to/esp-idf/components/esp_system/ubsan.c:223 + + 0x400db74f: test_ub at main.c:128 + + 0x400db99c: app_main at main.c:56 (discriminator 1) + +The types of errors reported by UBSAN can be as follows: + +.. list-table:: + :widths: 40 60 + :header-rows: 1 + + * - Name + - Meaning + * - ``type_mismatch``, ``type_mismatch_v1`` + - Incorrect pointer value: null, unaligned, not compatible with the given type. + * - ``add_overflow``, ``sub_overflow``, ``mul_overflow``, ``negate_overflow`` + - Integer overflow during addition, subtraction, multiplication, negation. + * - ``divrem_overflow`` + - Integer division by 0 or ``INT_MIN``. + * - ``shift_out_of_bounds`` + - Overflow in left or right shift operators. + * - ``out_of_bounds`` + - Access outside of bounds of an array. + * - ``unreachable`` + - Unreachable code executed. + * - ``missing_return`` + - Non-void function has reached its end without returning a value (C++ only). + * - ``vla_bound_not_positive`` + - Size of variable length array is not positive. + * - ``load_invalid_value`` + - Value of ``bool`` or ``enum`` (C++ only) variable is invalid (out of bounds). + * - ``nonnull_arg`` + - Null argument passed to a function which is declared with a ``nonnull`` attribute. + * - ``nonnull_return`` + - Null value returned from a function which is declared with ``returns_nonnull`` attribute. + * - ``builtin_unreachable`` + - ``__builtin_unreachable`` function called. + * - ``pointer_overflow`` + - Overflow in pointer arithmetic. diff --git a/tools/test_apps/system/panic/CMakeLists.txt b/tools/test_apps/system/panic/CMakeLists.txt index 7566025424..a53a793ff7 100644 --- a/tools/test_apps/system/panic/CMakeLists.txt +++ b/tools/test_apps/system/panic/CMakeLists.txt @@ -2,5 +2,27 @@ # in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.5) +set(COMPONENTS esptool_py main) include($ENV{IDF_PATH}/tools/cmake/project.cmake) + project(test_panic) + +# Enable UBSAN checks +# +# shift-base sanitizer is disabled due to the following pattern found in register header files: +# #define SOME_FIELD 0xFFFF +# #define SOME_FIELD_M ((SOME_FIELD_V)<<(SOME_FIELD_S)) +# #define SOME_FIELD_V 0xFFFF +# #define SOME_FIELD_S 16 +# here SOME_FIELD_V doesn't have an unsigned (U) prefix, so the compiler flags +# SOME_FIELD_M expansion (0xFFFF << 16) as generating integer overflow. +# +set(ubsan_options "-fsanitize=undefined" "-fno-sanitize=shift-base") + +# Only enable UBSAN for a few components related to the panic test, +# due to RAM size limitations. +foreach(component main espcoredump esp_system spi_flash + esp_common esp_hw_support soc hal freertos) + idf_component_get_property(lib ${component} COMPONENT_LIB) + target_compile_options(${lib} PRIVATE ${ubsan_options}) +endforeach() diff --git a/tools/test_apps/system/panic/app_test.py b/tools/test_apps/system/panic/app_test.py index 971ed2a735..e12d393f3b 100644 --- a/tools/test_apps/system/panic/app_test.py +++ b/tools/test_apps/system/panic/app_test.py @@ -292,5 +292,37 @@ def test_gdbstub_abort(env, _extra_data): test.abort_inner(env, 'gdbstub') +# test_ub + +@panic_test() +def test_panic_ub(env, _extra_data): + test.ub_inner(env, 'panic') + + +@panic_test() +def test_coredump_ub_uart_elf_crc(env, _extra_data): + test.ub_inner(env, 'coredump_uart_elf_crc') + + +@panic_test() +def test_coredump_ub_uart_bin_crc(env, _extra_data): + test.ub_inner(env, 'coredump_uart_bin_crc') + + +@panic_test() +def test_coredump_ub_flash_elf_sha(env, _extra_data): + test.ub_inner(env, 'coredump_flash_elf_sha') + + +@panic_test() +def test_coredump_ub_flash_bin_crc(env, _extra_data): + test.ub_inner(env, 'coredump_flash_bin_crc') + + +@panic_test() +def test_gdbstub_ub(env, _extra_data): + test.ub_inner(env, 'gdbstub') + + if __name__ == '__main__': run_all(__file__, sys.argv[1:]) diff --git a/tools/test_apps/system/panic/main/CMakeLists.txt b/tools/test_apps/system/panic/main/CMakeLists.txt index 186ac1d939..ba36e85dd4 100644 --- a/tools/test_apps/system/panic/main/CMakeLists.txt +++ b/tools/test_apps/system/panic/main/CMakeLists.txt @@ -1,2 +1,3 @@ idf_component_register(SRCS "test_panic_main.c" - INCLUDE_DIRS ".") + INCLUDE_DIRS "." + REQUIRES spi_flash esp_system) diff --git a/tools/test_apps/system/panic/main/test_panic_main.c b/tools/test_apps/system/panic/main/test_panic_main.c index b7e42a1c3f..aab2fbb9b3 100644 --- a/tools/test_apps/system/panic/main/test_panic_main.c +++ b/tools/test_apps/system/panic/main/test_panic_main.c @@ -22,6 +22,7 @@ static void test_int_wdt_cache_disabled(void); static void test_stack_overflow(void); static void test_illegal_instruction(void); static void test_instr_fetch_prohibited(void); +static void test_ub(void); void app_main(void) @@ -52,6 +53,7 @@ void app_main(void) HANDLE_TEST(test_stack_overflow); HANDLE_TEST(test_illegal_instruction); HANDLE_TEST(test_instr_fetch_prohibited); + HANDLE_TEST(test_ub); #undef HANDLE_TEST @@ -80,7 +82,7 @@ static void test_task_wdt(void) } } -static void test_storeprohibited(void) +static void __attribute__((no_sanitize_undefined)) test_storeprohibited(void) { *(int*) 0x1 = 0; } @@ -142,6 +144,12 @@ static void test_instr_fetch_prohibited(void) fptr(); } +static void test_ub(void) +{ + uint8_t stuff[1] = {rand()}; + printf("%d\n", stuff[rand()]); +} + /* implementations of the utility functions */ #define BOOT_CMD_MAX_LEN (128) @@ -183,7 +191,7 @@ static void die(const char* msg) { printf("Test error: %s\n\n", msg); fflush(stdout); - fsync(fileno(stdout)); + usleep(1000); /* Don't use abort here as it would enter the panic handler */ esp_restart_noos(); } diff --git a/tools/test_apps/system/panic/panic_tests.py b/tools/test_apps/system/panic/panic_tests.py index bb7967090e..bdf6b4941c 100644 --- a/tools/test_apps/system/panic/panic_tests.py +++ b/tools/test_apps/system/panic/panic_tests.py @@ -147,3 +147,15 @@ def instr_fetch_prohibited_inner(env, test_name): dut.expect_none('Guru Meditation') test_common(dut, test_name, expected_backtrace=['_init'] + get_default_backtrace(dut.test_name)) + + +def ub_inner(env, test_name): + with get_dut(env, test_name, 'test_ub') as dut: + dut.expect(re.compile(r'Undefined behavior of type out_of_bounds')) + dut.expect_backtrace() + dut.expect_elf_sha256() + dut.expect_none('Guru Meditation', 'Re-entered core dump') + test_common(dut, test_name, expected_backtrace=[ + # Backtrace interrupted when abort is called, IDF-842 + 'panic_abort', 'esp_system_abort' + ]) diff --git a/tools/test_apps/system/panic/sdkconfig.defaults b/tools/test_apps/system/panic/sdkconfig.defaults index 12b39b52fc..6f41f062fa 100644 --- a/tools/test_apps/system/panic/sdkconfig.defaults +++ b/tools/test_apps/system/panic/sdkconfig.defaults @@ -13,3 +13,6 @@ CONFIG_ESP_TASK_WDT_PANIC=y # For vTaskGetInfo() used in test_stack_overflow() CONFIG_FREERTOS_USE_TRACE_FACILITY=y + +# Reduce IRAM size +CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH=y