system: add option to enable undefined behavior sanitizer (UBSAN)

Closes https://github.com/espressif/esp-idf/issues/1574
pull/6974/head
Ivan Grokhotkov 2020-11-21 03:15:59 +01:00
rodzic 469c137c83
commit 9069f70db3
14 zmienionych plików z 486 dodań i 6 usunięć

Wyświetl plik

@ -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")

Wyświetl plik

@ -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

Wyświetl plik

@ -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)

Wyświetl plik

@ -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;

Wyświetl plik

@ -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 <stdbool.h>
#include <inttypes.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#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)
{
}

Wyświetl plik

@ -33,7 +33,7 @@ static inline uint32_t mpu_ll_id_to_addr(int id)
// 0xa0000000 = 5
// 0xc0000000 = 6
// 0xe0000000 = 7
return id * SOC_MPU_MIN_REGION_SIZE;
return (unsigned)id * SOC_MPU_MIN_REGION_SIZE;
}
static inline void mpu_ll_set_region_rw(uint32_t addr)

Wyświetl plik

@ -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<openthread/openthread/LICENSE>`.
* :component_file:` UBSAN runtime <esp_system/ubsan.c>` — Copyright (c) 2016, Linaro Limited and Jiří Zárevúcky, licensed under the BSD 2-clause license.
Build Tools
-----------

Wyświetl plik

@ -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 <https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html>`_ 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<cmake-build-properties>` and :ref:`component properties<cmake-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 <tools/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.

Wyświetl plik

@ -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()

Wyświetl plik

@ -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:])

Wyświetl plik

@ -1,2 +1,3 @@
idf_component_register(SRCS "test_panic_main.c"
INCLUDE_DIRS ".")
INCLUDE_DIRS "."
REQUIRES spi_flash esp_system)

Wyświetl plik

@ -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();
}

Wyświetl plik

@ -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"
])

Wyświetl plik

@ -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