diff --git a/components/bootloader_support/include/esp_flash_partitions.h b/components/bootloader_support/include/esp_flash_partitions.h index b1a83640ce..47fe975961 100644 --- a/components/bootloader_support/include/esp_flash_partitions.h +++ b/components/bootloader_support/include/esp_flash_partitions.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -33,6 +33,7 @@ extern "C" { #define PART_SUBTYPE_END 0xff #define PART_FLAG_ENCRYPTED (1<<0) +#define PART_FLAG_READONLY (1<<1) /* The md5sum value is found this many bytes after the ESP_PARTITION_MAGIC_MD5 offset */ #define ESP_PARTITION_MD5_OFFSET 16 @@ -92,6 +93,15 @@ typedef struct { */ esp_err_t esp_partition_table_verify(const esp_partition_info_t *partition_table, bool log_errors, int *num_partitions); +/** + * Check whether the region on the main flash is not read-only. + * + * @param addr Start address of the region + * @param size Size of the region + * + * @return true if the region is safe to write, otherwise false. + */ +bool esp_partition_is_flash_region_writable(size_t addr, size_t size); /** * Check whether the region on the main flash is safe to write. diff --git a/components/esp_common/include/esp_err.h b/components/esp_common/include/esp_err.h index ad2611f36b..30789475a5 100644 --- a/components/esp_common/include/esp_err.h +++ b/components/esp_common/include/esp_err.h @@ -34,6 +34,7 @@ typedef int esp_err_t; #define ESP_ERR_INVALID_VERSION 0x10A /*!< Version was invalid */ #define ESP_ERR_INVALID_MAC 0x10B /*!< MAC address was invalid */ #define ESP_ERR_NOT_FINISHED 0x10C /*!< Operation has not fully completed */ +#define ESP_ERR_NOT_ALLOWED 0x10D /*!< Operation is not allowed */ #define ESP_ERR_WIFI_BASE 0x3000 /*!< Starting number of WiFi error codes */ diff --git a/components/esp_common/src/esp_err_to_name.c b/components/esp_common/src/esp_err_to_name.c index 6e17f8519f..7aca0e17e4 100644 --- a/components/esp_common/src/esp_err_to_name.c +++ b/components/esp_common/src/esp_err_to_name.c @@ -133,6 +133,9 @@ static const esp_err_msg_t esp_err_msg_table[] = { # endif # ifdef ESP_ERR_NOT_FINISHED ERR_TBL_IT(ESP_ERR_NOT_FINISHED), /* 268 0x10c Operation has not fully completed */ +# endif +# ifdef ESP_ERR_NOT_ALLOWED + ERR_TBL_IT(ESP_ERR_NOT_ALLOWED), /* 269 0x10d Operation is not allowed */ # endif // components/nvs_flash/include/nvs.h # ifdef ESP_ERR_NVS_BASE diff --git a/components/esp_partition/include/esp_partition.h b/components/esp_partition/include/esp_partition.h index b9950c96ef..b4f8c2d2e5 100644 --- a/components/esp_partition/include/esp_partition.h +++ b/components/esp_partition/include/esp_partition.h @@ -132,6 +132,7 @@ typedef struct { uint32_t erase_size; /*!< size the erase operation should be aligned to */ char label[17]; /*!< partition label, zero-terminated ASCII string */ bool encrypted; /*!< flag is set to true if partition is encrypted */ + bool readonly; /*!< flag is set to true if partition is read-only */ } esp_partition_t; /** @@ -270,6 +271,7 @@ esp_err_t esp_partition_read(const esp_partition_t* partition, * @return ESP_OK, if data was written successfully; * ESP_ERR_INVALID_ARG, if dst_offset exceeds partition size; * ESP_ERR_INVALID_SIZE, if write would go out of bounds of the partition; + * ESP_ERR_NOT_ALLOWED, if partition is read-only; * or one of error codes from lower-level flash driver. */ esp_err_t esp_partition_write(const esp_partition_t* partition, @@ -322,6 +324,7 @@ esp_err_t esp_partition_read_raw(const esp_partition_t* partition, * @return ESP_OK, if data was written successfully; * ESP_ERR_INVALID_ARG, if dst_offset exceeds partition size; * ESP_ERR_INVALID_SIZE, if write would go out of bounds of the partition; + * ESP_ERR_NOT_ALLOWED, if partition is read-only; * or one of the error codes from lower-level flash driver. */ esp_err_t esp_partition_write_raw(const esp_partition_t* partition, @@ -341,6 +344,7 @@ esp_err_t esp_partition_write_raw(const esp_partition_t* partition, * @return ESP_OK, if the range was erased successfully; * ESP_ERR_INVALID_ARG, if iterator or dst are NULL; * ESP_ERR_INVALID_SIZE, if erase would go out of bounds of the partition; + * ESP_ERR_NOT_ALLOWED, if partition is read-only; * or one of error codes from lower-level flash driver. */ esp_err_t esp_partition_erase_range(const esp_partition_t* partition, diff --git a/components/esp_partition/partition.c b/components/esp_partition/partition.c index 640718a6b1..ae4ca4b7ba 100644 --- a/components/esp_partition/partition.c +++ b/components/esp_partition/partition.c @@ -154,6 +154,7 @@ static esp_err_t load_partitions(void) item->info.type = entry.type; item->info.subtype = entry.subtype; item->info.encrypted = entry.flags & PART_FLAG_ENCRYPTED; + item->info.readonly = entry.flags & PART_FLAG_READONLY; item->user_registered = false; #if CONFIG_IDF_TARGET_LINUX @@ -349,7 +350,6 @@ const esp_partition_t *esp_partition_find_first(esp_partition_type_t type, return res; } - void esp_partition_iterator_release(esp_partition_iterator_t iterator) { // iterator == NULL is okay @@ -384,6 +384,7 @@ const esp_partition_t *esp_partition_verify(const esp_partition_t *partition) esp_partition_iterator_release(it); return NULL; } + esp_err_t esp_partition_register_external(esp_flash_t *flash_chip, size_t offset, size_t size, const char *label, esp_partition_type_t type, esp_partition_subtype_t subtype, const esp_partition_t **out_partition) diff --git a/components/esp_partition/partition_linux.c b/components/esp_partition/partition_linux.c index 6af40c5067..cf3e86491e 100644 --- a/components/esp_partition/partition_linux.c +++ b/components/esp_partition/partition_linux.c @@ -369,6 +369,9 @@ esp_err_t esp_partition_write(const esp_partition_t *partition, size_t dst_offse { assert(partition != NULL && s_spiflash_mem_file_buf != NULL); + if (partition->readonly) { + return ESP_ERR_NOT_ALLOWED; + } if (partition->encrypted) { return ESP_ERR_NOT_SUPPORTED; } @@ -450,6 +453,9 @@ esp_err_t esp_partition_erase_range(const esp_partition_t *partition, size_t off { assert(partition != NULL); + if (partition->readonly) { + return ESP_ERR_NOT_ALLOWED; + } if (offset > partition->size || offset % partition->erase_size != 0) { return ESP_ERR_INVALID_ARG; } diff --git a/components/esp_partition/partition_target.c b/components/esp_partition/partition_target.c index 25456e919c..26a86bcc7f 100644 --- a/components/esp_partition/partition_target.c +++ b/components/esp_partition/partition_target.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -64,6 +64,9 @@ esp_err_t esp_partition_write(const esp_partition_t *partition, size_t dst_offset, const void *src, size_t size) { assert(partition != NULL); + if (partition->readonly) { + return ESP_ERR_NOT_ALLOWED; + } if (dst_offset > partition->size) { return ESP_ERR_INVALID_ARG; } @@ -103,6 +106,9 @@ esp_err_t esp_partition_write_raw(const esp_partition_t *partition, size_t dst_offset, const void *src, size_t size) { assert(partition != NULL); + if (partition->readonly) { + return ESP_ERR_NOT_ALLOWED; + } if (dst_offset > partition->size) { return ESP_ERR_INVALID_ARG; } @@ -118,6 +124,9 @@ esp_err_t esp_partition_erase_range(const esp_partition_t *partition, size_t offset, size_t size) { assert(partition != NULL); + if (partition->readonly) { + return ESP_ERR_NOT_ALLOWED; + } if (offset > partition->size) { return ESP_ERR_INVALID_ARG; } @@ -193,9 +202,25 @@ bool esp_partition_check_identity(const esp_partition_t *partition_1, const esp_ return false; } +bool esp_partition_is_flash_region_writable(size_t addr, size_t size) +{ + esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL); + for (; it != NULL; it = esp_partition_next(it)) { + const esp_partition_t *p = esp_partition_get(it); + if (p->readonly) { + if (addr >= p->address && addr < p->address + p->size) { + return false; + } + if (addr < p->address && addr + size > p->address) { + return false; + } + } + } + return true; +} + bool esp_partition_main_flash_region_safe(size_t addr, size_t size) { - bool result = true; if (addr <= ESP_PARTITION_TABLE_OFFSET + ESP_PARTITION_TABLE_MAX_LEN) { return false; } @@ -206,5 +231,5 @@ bool esp_partition_main_flash_region_safe(size_t addr, size_t size) if (addr < p->address && addr + size > p->address) { return false; } - return result; + return true; } diff --git a/components/fatfs/diskio/diskio_wl.c b/components/fatfs/diskio/diskio_wl.c index 849c6b98a7..8e20290e8c 100644 --- a/components/fatfs/diskio/diskio_wl.c +++ b/components/fatfs/diskio/diskio_wl.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ diff --git a/components/fatfs/vfs/vfs_fat_spiflash.c b/components/fatfs/vfs/vfs_fat_spiflash.c index d227de821b..0db2f70e0a 100644 --- a/components/fatfs/vfs/vfs_fat_spiflash.c +++ b/components/fatfs/vfs/vfs_fat_spiflash.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -8,7 +8,6 @@ #include #include "esp_check.h" #include "esp_log.h" -#include "esp_vfs.h" #include "esp_vfs_fat.h" #include "vfs_fat_internal.h" #include "diskio_impl.h" @@ -29,6 +28,8 @@ typedef struct vfs_fat_spiflash_ctx_t { static vfs_fat_spiflash_ctx_t *s_ctx[FF_VOLUMES] = {}; +extern esp_err_t esp_vfs_set_readonly_flag(const char* base_path); // from vfs/vfs.c to set readonly flag in esp_vfs_t struct externally + static bool s_get_context_id_by_label(const char *label, uint32_t *out_id) { vfs_fat_spiflash_ctx_t *p_ctx = NULL; @@ -160,6 +161,10 @@ esp_err_t esp_vfs_fat_spiflash_mount_rw_wl(const char* base_path, assert(ctx_id != FF_VOLUMES); s_ctx[ctx_id] = ctx; + if (data_partition->readonly) { + esp_vfs_set_readonly_flag(base_path); + } + return ESP_OK; fail: @@ -296,6 +301,11 @@ esp_err_t esp_vfs_fat_spiflash_mount_ro(const char* base_path, ret = ESP_FAIL; goto fail; } + + if (data_partition->readonly) { + esp_vfs_set_readonly_flag(base_path); + } + return ESP_OK; fail: diff --git a/components/nvs_flash/host_test/fixtures/test_fixtures.hpp b/components/nvs_flash/host_test/fixtures/test_fixtures.hpp index fdf2c110e3..03172d7db0 100644 --- a/components/nvs_flash/host_test/fixtures/test_fixtures.hpp +++ b/components/nvs_flash/host_test/fixtures/test_fixtures.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -76,6 +76,11 @@ public: return size; } + bool get_readonly() override + { + return partition.readonly; + } + const esp_partition_t partition; private: diff --git a/components/nvs_flash/include/nvs.h b/components/nvs_flash/include/nvs.h index 49fe9b7d60..2384680fee 100644 --- a/components/nvs_flash/include/nvs.h +++ b/components/nvs_flash/include/nvs.h @@ -135,6 +135,7 @@ typedef struct nvs_opaque_iterator_t *nvs_iterator_t; * - ESP_ERR_NO_MEM in case memory could not be allocated for the internal structures * - ESP_ERR_NVS_NOT_ENOUGH_SPACE if there is no space for a new entry or there are too many different * namespaces (maximum allowed different namespaces: 254) + * - ESP_ERR_NOT_ALLOWED if the NVS partition is read-only and mode is NVS_READWRITE * - other error codes from the underlying storage driver */ esp_err_t nvs_open(const char* namespace_name, nvs_open_mode_t open_mode, nvs_handle_t *out_handle); @@ -166,6 +167,7 @@ esp_err_t nvs_open(const char* namespace_name, nvs_open_mode_t open_mode, nvs_ha * - ESP_ERR_NO_MEM in case memory could not be allocated for the internal structures * - ESP_ERR_NVS_NOT_ENOUGH_SPACE if there is no space for a new entry or there are too many different * namespaces (maximum allowed different namespaces: 254) + * - ESP_ERR_NOT_ALLOWED if the NVS partition is read-only and mode is NVS_READWRITE * - other error codes from the underlying storage driver */ esp_err_t nvs_open_from_partition(const char *part_name, const char* namespace_name, nvs_open_mode_t open_mode, nvs_handle_t *out_handle); diff --git a/components/nvs_flash/include/nvs_handle.hpp b/components/nvs_flash/include/nvs_handle.hpp index b09d013d22..2421e1a861 100644 --- a/components/nvs_flash/include/nvs_handle.hpp +++ b/components/nvs_flash/include/nvs_handle.hpp @@ -222,6 +222,7 @@ protected: * - ESP_ERR_NVS_NOT_FOUND id namespace doesn't exist yet and * mode is NVS_READONLY * - ESP_ERR_NVS_INVALID_NAME if namespace name doesn't satisfy constraints + * - ESP_ERR_NOT_ALLOWED if the NVS partition is read-only and mode is NVS_READWRITE * - other error codes from the underlying storage driver * * @return unique pointer of an nvs handle on success, an empty unique pointer otherwise diff --git a/components/nvs_flash/src/nvs_partition.cpp b/components/nvs_flash/src/nvs_partition.cpp index f1b7d36d2d..ec15d58baa 100644 --- a/components/nvs_flash/src/nvs_partition.cpp +++ b/components/nvs_flash/src/nvs_partition.cpp @@ -1,16 +1,8 @@ -// Copyright 2019 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * SPDX-FileCopyrightText: 2019-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #include #include "nvs_partition.hpp" @@ -74,4 +66,9 @@ uint32_t NVSPartition::get_size() return mESPPartition->size; } +bool NVSPartition::get_readonly() +{ + return mESPPartition->readonly; +} + } // nvs diff --git a/components/nvs_flash/src/nvs_partition.hpp b/components/nvs_flash/src/nvs_partition.hpp index 8ed544a6b1..e8f64afc54 100644 --- a/components/nvs_flash/src/nvs_partition.hpp +++ b/components/nvs_flash/src/nvs_partition.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2019-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -99,6 +99,11 @@ public: */ uint32_t get_size() override; + /** + * @return true if the partition is read-only. + */ + bool get_readonly() override; + protected: const esp_partition_t* mESPPartition; }; diff --git a/components/nvs_flash/src/nvs_partition_manager.cpp b/components/nvs_flash/src/nvs_partition_manager.cpp index f43ca130a1..eecde60ea5 100644 --- a/components/nvs_flash/src/nvs_partition_manager.cpp +++ b/components/nvs_flash/src/nvs_partition_manager.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -192,6 +192,11 @@ esp_err_t NVSPartitionManager::open_handle(const char *part_name, return ESP_ERR_NVS_PART_NOT_FOUND; } + if (open_mode == NVS_READWRITE && const_cast(sHandle->getPart())->get_readonly()) { + return ESP_ERR_NOT_ALLOWED; + } + + esp_err_t err = sHandle->createOrOpenNamespace(ns_name, open_mode == NVS_READWRITE, nsIndex); if (err != ESP_OK) { return err; diff --git a/components/nvs_flash/src/partition.hpp b/components/nvs_flash/src/partition.hpp index 4cbfb6d9dd..e649f84b20 100644 --- a/components/nvs_flash/src/partition.hpp +++ b/components/nvs_flash/src/partition.hpp @@ -1,16 +1,8 @@ -// Copyright 2019 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * SPDX-FileCopyrightText: 2019-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #ifndef PARTITION_HPP_ #define PARTITION_HPP_ @@ -52,6 +44,11 @@ public: * Return the partition size in bytes. */ virtual uint32_t get_size() = 0; + + /** + * Return true if the partition is read-only. + */ + virtual bool get_readonly() = 0; }; } // nvs diff --git a/components/nvs_flash/test_nvs_host/test_fixtures.hpp b/components/nvs_flash/test_nvs_host/test_fixtures.hpp index 473f1da973..4a50a1daad 100644 --- a/components/nvs_flash/test_nvs_host/test_fixtures.hpp +++ b/components/nvs_flash/test_nvs_host/test_fixtures.hpp @@ -1,16 +1,8 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #include "nvs_partition.hpp" #include "nvs_encrypted_partition.hpp" #include "spi_flash_emulation.h" @@ -27,6 +19,7 @@ public: assert(partition_name); assert(flash_emu); assert(size); + readonly = false; } const char *get_partition_name() override @@ -101,6 +94,11 @@ public: return size; } + bool get_readonly() override + { + return readonly; + } + private: const char *partition_name; @@ -109,6 +107,8 @@ private: uint32_t address; uint32_t size; + + bool readonly; }; struct PartitionEmulationFixture { diff --git a/components/partition_table/gen_esp32part.py b/components/partition_table/gen_esp32part.py index 5eb8475539..5e18a86ed0 100755 --- a/components/partition_table/gen_esp32part.py +++ b/components/partition_table/gen_esp32part.py @@ -7,7 +7,7 @@ # See https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/partition-tables.html # for explanation of partition table structure and uses. # -# SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 from __future__ import division, print_function, unicode_literals @@ -32,7 +32,7 @@ SECURE_NONE = None SECURE_V1 = 'v1' SECURE_V2 = 'v2' -__version__ = '1.2' +__version__ = '1.3' APP_TYPE = 0x00 DATA_TYPE = 0x01 @@ -341,7 +341,8 @@ class PartitionDefinition(object): # dictionary maps flag name (as used in CSV flags list, property name) # to bit set in flags words in binary format FLAGS = { - 'encrypted': 0 + 'encrypted': 0, + 'readonly': 1 } # add subtypes for the 16 OTA slot values ("ota_XX, etc.") @@ -355,6 +356,7 @@ class PartitionDefinition(object): self.offset = None self.size = None self.encrypted = False + self.readonly = False @classmethod def from_csv(cls, line, line_no): @@ -454,6 +456,11 @@ class PartitionDefinition(object): critical("WARNING: Partition has name '%s' which is a partition subtype, but this partition has " 'non-matching type 0x%x and subtype 0x%x. Mistake in partition table?' % (self.name, self.type, self.subtype)) + always_rw_data_subtypes = [SUBTYPES[DATA_TYPE]['ota'], SUBTYPES[DATA_TYPE]['coredump']] + if self.type == TYPES['data'] and self.subtype in always_rw_data_subtypes and self.readonly is True: + raise ValidationError(self, "'%s' partition of type %s and subtype %s is always read-write and cannot be read-only" % + (self.name, self.type, self.subtype)) + STRUCT_FORMAT = b'<2sBBLL16sL' @classmethod diff --git a/components/partition_table/parttool.py b/components/partition_table/parttool.py index 3c30d850cc..2b6920cb59 100755 --- a/components/partition_table/parttool.py +++ b/components/partition_table/parttool.py @@ -3,7 +3,7 @@ # parttool is used to perform partition level operations - reading, # writing, erasing and getting info about the partition. # -# SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2018-2023 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 from __future__ import division, print_function @@ -16,7 +16,7 @@ import tempfile import gen_esp32part as gen -__version__ = '2.0' +__version__ = '2.1' COMPONENTS_PATH = os.path.expandvars(os.path.join('$IDF_PATH', 'components')) ESPTOOL_PY = os.path.join(COMPONENTS_PATH, 'esptool_py', 'esptool', 'esptool.py') @@ -159,10 +159,13 @@ class ParttoolTarget(): self._call_esptool(['read_flash', str(partition.offset), str(partition.size), output] + self.esptool_read_args) def write_partition(self, partition_id, input): - self.erase_partition(partition_id) - partition = self.get_partition_info(partition_id) + if partition.readonly: + raise Exception(f'"{partition.name}" partition is read-only') + + self.erase_partition(partition_id) + with open(input, 'rb') as input_file: content_len = len(input_file.read()) @@ -209,7 +212,8 @@ def _get_partition_info(target, partition_id, info): 'subtype': '{}'.format(p.subtype), 'offset': '0x{:x}'.format(p.offset), 'size': '0x{:x}'.format(p.size), - 'encrypted': '{}'.format(p.encrypted) + 'encrypted': '{}'.format(p.encrypted), + 'readonly': '{}'.format(p.readonly) } for i in info: infos += [info_dict[i]] @@ -269,7 +273,8 @@ def main(): print_partition_info_subparser = subparsers.add_parser('get_partition_info', help='get partition information', parents=[partition_selection_parser]) print_partition_info_subparser.add_argument('--info', help='type of partition information to get', - choices=['name', 'type', 'subtype', 'offset', 'size', 'encrypted'], default=['offset', 'size'], nargs='+') + choices=['name', 'type', 'subtype', 'offset', 'size', 'encrypted', 'readonly'], + default=['offset', 'size'], nargs='+') print_partition_info_subparser.add_argument('--part_list', help='Get a list of partitions suitable for a given type', action='store_true') args = parser.parse_args() @@ -329,10 +334,10 @@ def main(): # Create the operation table and execute the operation common_args = {'target':target, 'partition_id':partition_id} parttool_ops = { - 'erase_partition':(_erase_partition, []), - 'read_partition':(_read_partition, ['output']), - 'write_partition':(_write_partition, ['input']), - 'get_partition_info':(_get_partition_info, ['info']) + 'erase_partition': (_erase_partition, []), + 'read_partition': (_read_partition, ['output']), + 'write_partition': (_write_partition, ['input']), + 'get_partition_info': (_get_partition_info, ['info']) } (op, op_args) = parttool_ops[args.operation] diff --git a/components/spi_flash/esp_flash_api.c b/components/spi_flash/esp_flash_api.c index a3a932cf0e..f639a37fcb 100644 --- a/components/spi_flash/esp_flash_api.c +++ b/components/spi_flash/esp_flash_api.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -47,25 +47,26 @@ DRAM_ATTR static const char TAG[] = "spi_flash"; /* CHECK_WRITE_ADDRESS macro to fail writes which land in the bootloader, partition table, or running application region. */ -#if CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED -#define CHECK_WRITE_ADDRESS(CHIP, ADDR, SIZE) -#else /* FAILS or ABORTS */ -#define CHECK_WRITE_ADDRESS(CHIP, ADDR, SIZE) do { \ - if (CHIP && CHIP->os_func->region_protected && CHIP->os_func->region_protected(CHIP->os_func_data, ADDR, SIZE)) { \ - UNSAFE_WRITE_ADDRESS; \ - } \ +#define CHECK_WRITE_ADDRESS(CHIP, ADDR, SIZE) do { \ + if (CHIP && CHIP->os_func->region_protected) { \ + esp_err_t ret = CHIP->os_func->region_protected(CHIP->os_func_data, ADDR, SIZE); \ + if (ret == ESP_ERR_NOT_ALLOWED) { \ + return ret; /* ESP_ERR_NOT_ALLOWED from read-only partition check */ \ + } else if (ret != ESP_OK) { \ + UNSAFE_WRITE_ADDRESS; /* FAILS or ABORTS */ \ + } \ + } \ } while(0) -#endif // CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED /* Convenience macro for beginning of all API functions. * Check the return value of `rom_spiflash_api_funcs->chip_check` is correct, * and the chip supports the operation in question. */ -#define VERIFY_CHIP_OP(op) do { \ +#define VERIFY_CHIP_OP(op) do { \ if (err != ESP_OK) return err; \ - if (chip->chip_drv->op == NULL) { \ - return ESP_ERR_FLASH_UNSUPPORTED_CHIP; \ - } \ + if (chip->chip_drv->op == NULL) { \ + return ESP_ERR_FLASH_UNSUPPORTED_CHIP; \ + } \ } while (0) diff --git a/components/spi_flash/include/esp_flash.h b/components/spi_flash/include/esp_flash.h index e5b92b9412..9eedaa753f 100644 --- a/components/spi_flash/include/esp_flash.h +++ b/components/spi_flash/include/esp_flash.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -191,6 +191,7 @@ esp_err_t esp_flash_read_unique_chip_id(esp_flash_t *chip, uint64_t *out_id); * @return * - ESP_OK on success, * - ESP_ERR_NOT_SUPPORTED if the chip is not able to perform the operation. This is indicated by WREN = 1 after the command is sent. + * - ESP_ERR_NOT_ALLOWED if a read-only partition is present. * - Other flash error code if operation failed. */ esp_err_t esp_flash_erase_chip(esp_flash_t *chip); @@ -211,6 +212,7 @@ esp_err_t esp_flash_erase_chip(esp_flash_t *chip); * @return * - ESP_OK on success, * - ESP_ERR_NOT_SUPPORTED if the chip is not able to perform the operation. This is indicated by WREN = 1 after the command is sent. + * - ESP_ERR_NOT_ALLOWED if the address range (start -- start + len) overlaps with a read-only partition address space * - Other flash error code if operation failed. */ esp_err_t esp_flash_erase_region(esp_flash_t *chip, uint32_t start, uint32_t len); @@ -316,9 +318,10 @@ esp_err_t esp_flash_read(esp_flash_t *chip, void *buffer, uint32_t address, uint * There are no alignment constraints on buffer, address or length. * * @return - * - ESP_OK on success, + * - ESP_OK on success * - ESP_FAIL, bad write, this will be detected only when CONFIG_SPI_FLASH_VERIFY_WRITE is enabled * - ESP_ERR_NOT_SUPPORTED if the chip is not able to perform the operation. This is indicated by WREN = 1 after the command is sent. + * - ESP_ERR_NOT_ALLOWED if the address range (address -- address + length) overlaps with a read-only partition address space * - Other flash error code if operation failed. */ esp_err_t esp_flash_write(esp_flash_t *chip, const void *buffer, uint32_t address, uint32_t length); @@ -337,6 +340,7 @@ esp_err_t esp_flash_write(esp_flash_t *chip, const void *buffer, uint32_t addres * - ESP_FAIL: bad write, this will be detected only when CONFIG_SPI_FLASH_VERIFY_WRITE is enabled * - ESP_ERR_NOT_SUPPORTED: encrypted write not supported for this chip. * - ESP_ERR_INVALID_ARG: Either the address, buffer or length is invalid. + * - ESP_ERR_NOT_ALLOWED if the address range (address -- address + length) overlaps with a read-only partition address space */ esp_err_t esp_flash_write_encrypted(esp_flash_t *chip, uint32_t address, const void *buffer, uint32_t length); diff --git a/components/spi_flash/spi_flash_os_func_app.c b/components/spi_flash/spi_flash_os_func_app.c index f242bb46b3..31aa11b8a9 100644 --- a/components/spi_flash/spi_flash_os_func_app.c +++ b/components/spi_flash/spi_flash_os_func_app.c @@ -206,15 +206,20 @@ static IRAM_ATTR void release_buffer_malloc(void* arg, void *temp_buf) static IRAM_ATTR esp_err_t main_flash_region_protected(void* arg, size_t start_addr, size_t size) { + if (!esp_partition_is_flash_region_writable(start_addr, size)) { + return ESP_ERR_NOT_ALLOWED; + } +#if !CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED if (((app_func_arg_t*)arg)->no_protect || esp_partition_main_flash_region_safe(start_addr, size)) { //ESP_OK = 0, also means protected==0 return ESP_OK; } else { return ESP_ERR_NOT_SUPPORTED; } +#endif // !CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED + return ESP_OK; } - static IRAM_ATTR void main_flash_op_status(uint32_t op_status) { bool is_erasing = op_status & SPI_FLASH_OS_IS_ERASING_STATUS_FLAG; diff --git a/components/spiffs/esp_spiffs.c b/components/spiffs/esp_spiffs.c index c6d4a5b1ed..5731b322b7 100644 --- a/components/spiffs/esp_spiffs.c +++ b/components/spiffs/esp_spiffs.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -402,8 +402,24 @@ esp_err_t esp_spiffs_gc(const char* partition_label, size_t size_to_gc) esp_err_t esp_vfs_spiffs_register(const esp_vfs_spiffs_conf_t * conf) { assert(conf->base_path); + + esp_err_t err = esp_spiffs_init(conf); + if (err != ESP_OK) { + return err; + } + + int index; + if (esp_spiffs_by_label(conf->partition_label, &index) != ESP_OK) { + return ESP_ERR_INVALID_STATE; + } + + int vfs_flags = ESP_VFS_FLAG_CONTEXT_PTR; + if (_efs[index]->partition->readonly) { + vfs_flags |= ESP_VFS_FLAG_READONLY_FS; + } + const esp_vfs_t vfs = { - .flags = ESP_VFS_FLAG_CONTEXT_PTR, + .flags = vfs_flags, .write_p = &vfs_spiffs_write, .lseek_p = &vfs_spiffs_lseek, .read_p = &vfs_spiffs_read, @@ -433,16 +449,6 @@ esp_err_t esp_vfs_spiffs_register(const esp_vfs_spiffs_conf_t * conf) #endif // CONFIG_VFS_SUPPORT_DIR }; - esp_err_t err = esp_spiffs_init(conf); - if (err != ESP_OK) { - return err; - } - - int index; - if (esp_spiffs_by_label(conf->partition_label, &index) != ESP_OK) { - return ESP_ERR_INVALID_STATE; - } - strlcat(_efs[index]->base_path, conf->base_path, ESP_VFS_PATH_MAX + 1); err = esp_vfs_register(conf->base_path, &vfs, _efs[index]); if (err != ESP_OK) { diff --git a/components/vfs/include/esp_vfs.h b/components/vfs/include/esp_vfs.h index f9e5e785be..ac0029e3f1 100644 --- a/components/vfs/include/esp_vfs.h +++ b/components/vfs/include/esp_vfs.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -53,6 +53,11 @@ extern "C" { */ #define ESP_VFS_FLAG_CONTEXT_PTR 1 +/** + * Flag which indicates that FS is located on read-only partition. + */ +#define ESP_VFS_FLAG_READONLY_FS 2 + /* * @brief VFS identificator used for esp_vfs_register_with_id() */ @@ -91,7 +96,7 @@ typedef struct */ typedef struct { - int flags; /*!< ESP_VFS_FLAG_CONTEXT_PTR or ESP_VFS_FLAG_DEFAULT */ + int flags; /*!< ESP_VFS_FLAG_CONTEXT_PTR and/or ESP_VFS_FLAG_READONLY_FS or ESP_VFS_FLAG_DEFAULT */ union { ssize_t (*write_p)(void* p, int fd, const void * data, size_t size); /*!< Write with context pointer */ ssize_t (*write)(int fd, const void * data, size_t size); /*!< Write without context pointer */ diff --git a/components/vfs/vfs.c b/components/vfs/vfs.c index 6adf9db139..d68699af05 100644 --- a/components/vfs/vfs.c +++ b/components/vfs/vfs.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -270,6 +270,29 @@ esp_err_t esp_vfs_unregister_fd(esp_vfs_id_t vfs_id, int fd) return ret; } +/* + * Set ESP_VFS_FLAG_READONLY_FS read-only flag for a registered virtual filesystem + * for given path prefix. Should be only called from the esp_vfs_*filesystem* register + * or helper mount functions where vfs_t is not available to set the read-only + * flag directly (e.g. esp_vfs_fat_spiflash_mount_rw_wl). + */ +esp_err_t esp_vfs_set_readonly_flag(const char* base_path) +{ + const size_t base_path_len = strlen(base_path); + for (size_t i = 0; i < s_vfs_count; ++i) { + vfs_entry_t* vfs = s_vfs[i]; + if (vfs == NULL) { + continue; + } + if (base_path_len == vfs->path_prefix_len && + memcmp(base_path, vfs->path_prefix, vfs->path_prefix_len) == 0) { + vfs->vfs.flags |= ESP_VFS_FLAG_READONLY_FS; + return ESP_OK; + } + } + return ESP_ERR_INVALID_STATE; +} + const vfs_entry_t *get_vfs_for_index(int index) { if (index < 0 || index >= s_vfs_count) { @@ -401,6 +424,12 @@ const vfs_entry_t* get_vfs_for_path(const char* path) ret = (*pvfs->vfs.func)(__VA_ARGS__);\ } +#define CHECK_VFS_READONLY_FLAG(flags) \ + if (flags & ESP_VFS_FLAG_READONLY_FS) { \ + __errno_r(r) = EROFS; \ + return -1; \ + } + int esp_vfs_open(struct _reent *r, const char * path, int flags, int mode) { const vfs_entry_t *vfs = get_vfs_for_path(path); @@ -408,6 +437,14 @@ int esp_vfs_open(struct _reent *r, const char * path, int flags, int mode) __errno_r(r) = ENOENT; return -1; } + + int acc_mode = flags & O_ACCMODE; + int ro_filesystem = vfs->vfs.flags & ESP_VFS_FLAG_READONLY_FS; + if (acc_mode != O_RDONLY && ro_filesystem) { + __errno_r(r) = EROFS; + return -1; + } + const char *path_within_vfs = translate_path(vfs, path); int fd_within_vfs; CHECK_AND_CALL(fd_within_vfs, r, vfs, open, path_within_vfs, flags, mode); @@ -621,6 +658,9 @@ int esp_vfs_link(struct _reent *r, const char* n1, const char* n2) __errno_r(r) = EXDEV; return -1; } + + CHECK_VFS_READONLY_FLAG(vfs2->vfs.flags); + const char* path1_within_vfs = translate_path(vfs, n1); const char* path2_within_vfs = translate_path(vfs, n2); int ret; @@ -635,6 +675,9 @@ int esp_vfs_unlink(struct _reent *r, const char *path) __errno_r(r) = ENOENT; return -1; } + + CHECK_VFS_READONLY_FLAG(vfs->vfs.flags); + const char* path_within_vfs = translate_path(vfs, path); int ret; CHECK_AND_CALL(ret, r, vfs, unlink, path_within_vfs); @@ -648,11 +691,17 @@ int esp_vfs_rename(struct _reent *r, const char *src, const char *dst) __errno_r(r) = ENOENT; return -1; } + + CHECK_VFS_READONLY_FLAG(vfs->vfs.flags); + const vfs_entry_t* vfs_dst = get_vfs_for_path(dst); if (vfs != vfs_dst) { __errno_r(r) = EXDEV; return -1; } + + CHECK_VFS_READONLY_FLAG(vfs_dst->vfs.flags); + const char* src_within_vfs = translate_path(vfs, src); const char* dst_within_vfs = translate_path(vfs, dst); int ret; @@ -753,6 +802,9 @@ int esp_vfs_mkdir(const char* name, mode_t mode) __errno_r(r) = ENOENT; return -1; } + + CHECK_VFS_READONLY_FLAG(vfs->vfs.flags); + const char* path_within_vfs = translate_path(vfs, name); int ret; CHECK_AND_CALL(ret, r, vfs, mkdir, path_within_vfs, mode); @@ -767,6 +819,9 @@ int esp_vfs_rmdir(const char* name) __errno_r(r) = ENOENT; return -1; } + + CHECK_VFS_READONLY_FLAG(vfs->vfs.flags); + const char* path_within_vfs = translate_path(vfs, name); int ret; CHECK_AND_CALL(ret, r, vfs, rmdir, path_within_vfs); @@ -796,6 +851,9 @@ int esp_vfs_truncate(const char *path, off_t length) __errno_r(r) = ENOENT; return -1; } + + CHECK_VFS_READONLY_FLAG(vfs->vfs.flags); + const char* path_within_vfs = translate_path(vfs, path); CHECK_AND_CALL(ret, r, vfs, truncate, path_within_vfs, length); return ret; @@ -810,6 +868,9 @@ int esp_vfs_ftruncate(int fd, off_t length) __errno_r(r) = EBADF; return -1; } + + CHECK_VFS_READONLY_FLAG(vfs->vfs.flags); + int ret; CHECK_AND_CALL(ret, r, vfs, ftruncate, local_fd, length); return ret; diff --git a/components/wear_levelling/WL_Flash.cpp b/components/wear_levelling/WL_Flash.cpp index d82b250b9f..b3fd31e4ff 100644 --- a/components/wear_levelling/WL_Flash.cpp +++ b/components/wear_levelling/WL_Flash.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ diff --git a/components/wear_levelling/private_include/WL_Flash.h b/components/wear_levelling/private_include/WL_Flash.h index 58899ce831..5f24467e74 100644 --- a/components/wear_levelling/private_include/WL_Flash.h +++ b/components/wear_levelling/private_include/WL_Flash.h @@ -22,7 +22,7 @@ public : WL_Flash(); ~WL_Flash() override; - virtual esp_err_t config(wl_config_t *cfg, Partition *flash_drv); + virtual esp_err_t config(wl_config_t *cfg, Partition *partition); virtual esp_err_t init(); size_t get_flash_size() override; diff --git a/components/wear_levelling/wear_levelling.cpp b/components/wear_levelling/wear_levelling.cpp index 66baf6e3f7..cdbdc923c0 100644 --- a/components/wear_levelling/wear_levelling.cpp +++ b/components/wear_levelling/wear_levelling.cpp @@ -174,10 +174,12 @@ esp_err_t wl_unmount(wl_handle_t handle) _lock_acquire(&s_instances_lock); result = check_handle(handle, __func__); if (result == ESP_OK) { - // We have to flush state of the component - result = s_instances[handle].instance->flush(); // We use placement new in wl_mount, so call destructor directly Partition *part = s_instances[handle].instance->get_part(); + // We have to flush state of the component + if (!part->is_readonly()) { + result = s_instances[handle].instance->flush(); + } part->~Partition(); free(part); s_instances[handle].instance->~WL_Flash(); diff --git a/docs/en/api-guides/partition-tables.rst b/docs/en/api-guides/partition-tables.rst index 361f70d651..ea77d5183f 100644 --- a/docs/en/api-guides/partition-tables.rst +++ b/docs/en/api-guides/partition-tables.rst @@ -166,11 +166,21 @@ If you want the partitions in the partition table to work relative to any placem Flags ~~~~~ -Only one flag is currently supported, ``encrypted``. If this field is set to ``encrypted``, this partition will be encrypted if :doc:`/security/flash-encryption` is enabled. +Two flags are currently supported, ``encrypted`` and ``readonly``: + + - If ``encrypted`` flag is set, the partition will be encrypted if :doc:`/security/flash-encryption` is enabled. -.. note:: + .. note:: - ``app`` type partitions will always be encrypted, regardless of whether this flag is set or not. + ``app`` type partitions will always be encrypted, regardless of whether this flag is set or not. + + - If ``readonly`` flag is set, the partition will be read-only. This flag is only supported for ``data`` type partitions except ``ota``` and ``coredump``` subtypes. This flag can help to protect against the accidental writes to partition that contains critical device specific configuration data, e.g., factory data partition. + + .. note:: + + Using C file I/O API to open a file (``fopen```) in any write mode (``w``, ``w+``, ``a``, ``a+``, ``r+``) will fail and return ``NULL``. Using ``open`` with any other flag than ``O_RDONLY`` will fail and return ``-1`` while ``errno`` global variable will be set to ``EACCES``. This is also true for any other POSIX syscall function performing write or erase operations. Opening a handle in read-write mode for NVS on a read-only partition will fail and return :c:macro:`ESP_ERR_NOT_ALLOWED` error code. Using a lower level API like ``esp_partition``, ``spi_flash``, ``wear_levelling``, etc. to write to a read-only partition will result in :c:macro:`ESP_ERR_NOT_ALLOWED` error code. + +You can specify multiple flags by separating them with a colon. For example, ``encrypted:readonly``. Generating Binary Partition Table --------------------------------- diff --git a/tools/ci/check_copyright_ignore.txt b/tools/ci/check_copyright_ignore.txt index ae1ce2ce7f..e97764baa6 100644 --- a/tools/ci/check_copyright_ignore.txt +++ b/tools/ci/check_copyright_ignore.txt @@ -591,16 +591,13 @@ components/nvs_flash/src/nvs_handle_locked.cpp components/nvs_flash/src/nvs_handle_locked.hpp components/nvs_flash/src/nvs_item_hash_list.cpp components/nvs_flash/src/nvs_pagemanager.hpp -components/nvs_flash/src/nvs_partition.cpp components/nvs_flash/src/nvs_partition_lookup.cpp components/nvs_flash/src/nvs_partition_lookup.hpp components/nvs_flash/src/nvs_platform.hpp components/nvs_flash/src/nvs_test_api.h components/nvs_flash/src/nvs_types.cpp -components/nvs_flash/src/partition.hpp components/nvs_flash/test_nvs_host/main.cpp components/nvs_flash/test_nvs_host/sdkconfig.h -components/nvs_flash/test_nvs_host/test_fixtures.hpp components/nvs_flash/test_nvs_host/test_intrusive_list.cpp components/protocomm/include/transports/protocomm_console.h components/protocomm/include/transports/protocomm_httpd.h diff --git a/tools/test_apps/.build-test-rules.yml b/tools/test_apps/.build-test-rules.yml index fa7ac86587..f90c78a332 100644 --- a/tools/test_apps/.build-test-rules.yml +++ b/tools/test_apps/.build-test-rules.yml @@ -74,6 +74,23 @@ tools/test_apps/security/signed_app_no_secure_boot: temporary: true reason: No need to test on all targets +tools/test_apps/storage/partition_table_readonly: + disable_test: + - if: IDF_TARGET not in ["esp32", "esp32c3"] + reason: these chips should be sufficient for test coverage (Xtensa and RISC-V, single and dual core) + disable: + - if: CONFIG_NAME == "encrypted" + temporary: true + reason: there are potential bugs with pytest when using flash encryption and NVS partition with nvs_create_partition_image #TODO: IDF-8300 + depends_components: + - partition_table + - spi_flash + - esp_partition + - nvs_flash + - vfs + - fatfs + - spiffs + tools/test_apps/system/bootloader_sections: disable: - if: IDF_TARGET == "esp32c2" diff --git a/tools/test_apps/storage/partition_table_readonly/CMakeLists.txt b/tools/test_apps/storage/partition_table_readonly/CMakeLists.txt new file mode 100644 index 0000000000..df4e85de8c --- /dev/null +++ b/tools/test_apps/storage/partition_table_readonly/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(test_partition_table_readonly) diff --git a/tools/test_apps/storage/partition_table_readonly/README.md b/tools/test_apps/storage/partition_table_readonly/README.md new file mode 100644 index 0000000000..bf47d80ec6 --- /dev/null +++ b/tools/test_apps/storage/partition_table_readonly/README.md @@ -0,0 +1,2 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | diff --git a/tools/test_apps/storage/partition_table_readonly/filesystem_image/dir/dirf.txt b/tools/test_apps/storage/partition_table_readonly/filesystem_image/dir/dirf.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/test_apps/storage/partition_table_readonly/filesystem_image/hello.txt b/tools/test_apps/storage/partition_table_readonly/filesystem_image/hello.txt new file mode 100644 index 0000000000..f16e016fad --- /dev/null +++ b/tools/test_apps/storage/partition_table_readonly/filesystem_image/hello.txt @@ -0,0 +1 @@ +This is a file cointained in the generated filesystem image on the host and flashed to the ESP device diff --git a/tools/test_apps/storage/partition_table_readonly/main/CMakeLists.txt b/tools/test_apps/storage/partition_table_readonly/main/CMakeLists.txt new file mode 100644 index 0000000000..617d4cc7c8 --- /dev/null +++ b/tools/test_apps/storage/partition_table_readonly/main/CMakeLists.txt @@ -0,0 +1,16 @@ +idf_component_register(SRCS "main.c" + INCLUDE_DIRS ".") + +set(nvs_partition_name nvs_ro) +set(nvs_data_csv ../nvs_data.csv) +nvs_create_partition_image(${nvs_partition_name} ${nvs_data_csv} FLASH_IN_PROJECT) + +set(image ../filesystem_image) + +set(fatfs_wl_partition_name fatfs_ro) +set(fatfs_raw_partition_name fatfs_raw_ro) +fatfs_create_spiflash_image(${fatfs_wl_partition_name} ${image} FLASH_IN_PROJECT) +fatfs_create_rawflash_image(${fatfs_raw_partition_name} ${image} FLASH_IN_PROJECT) + +set(spiffs_partition_name spiffs_ro) +spiffs_create_partition_image(${spiffs_partition_name} ${image} FLASH_IN_PROJECT) diff --git a/tools/test_apps/storage/partition_table_readonly/main/main.c b/tools/test_apps/storage/partition_table_readonly/main/main.c new file mode 100644 index 0000000000..39126c8db5 --- /dev/null +++ b/tools/test_apps/storage/partition_table_readonly/main/main.c @@ -0,0 +1,359 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "unity.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "esp_flash.h" +#include +#include "nvs_flash.h" +#include "nvs.h" +#include "esp_vfs.h" +#include "esp_vfs_fat.h" +#include "esp_spiffs.h" +#include "esp_heap_caps.h" +#include "esp_flash_encrypt.h" +#include "esp_efuse_table.h" + +static const char* TAG = "test_readonly_partition_feature"; + +#define NUM_OF_READONLY_PARTITIONS 4 +const esp_partition_t* readonly_partitions[NUM_OF_READONLY_PARTITIONS]; + +// Partition names +const char *nvs_partition_name = "nvs_ro"; +const char *fatfs_wl_partition_name = "fatfs_ro"; +const char *fatfs_raw_partition_name = "fatfs_raw_ro"; +const char *spiffs_partition_name = "spiffs_ro"; + +// Mount paths for partitions +#define FATFS_WL_BASE_PATH "/fatfs_wl" +#define FATFS_RAW_BASE_PATH "/fatfs_raw" +#define SPIFFS_BASE_PATH "/spiffs" + +// Handle of the wear levelling library instance +static wl_handle_t s_wl_handle = WL_INVALID_HANDLE; + +// Data in each filesystem partition +const char* cmp_string = "This is a file cointained in the generated filesystem image on the host and flashed to the ESP device"; +#define CMP_STRING_LEN 102 // 101 + '\0' + +static void fill_array_of_readonly_data_partitions(void) +{ + // This finds read-only partitions defined in the partition table + const esp_partition_t* part_nvs = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, + ESP_PARTITION_SUBTYPE_ANY, nvs_partition_name); + const esp_partition_t* part_fatfs_wl = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, + ESP_PARTITION_SUBTYPE_ANY, fatfs_wl_partition_name); + const esp_partition_t* part_fatfs_raw = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, + ESP_PARTITION_SUBTYPE_ANY, fatfs_raw_partition_name); + const esp_partition_t* part_spiffs = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, + ESP_PARTITION_SUBTYPE_ANY, spiffs_partition_name); + TEST_ASSERT_NOT_NULL(part_nvs); // NULL means partition table set wrong + TEST_ASSERT_NOT_NULL(part_fatfs_wl); + TEST_ASSERT_NOT_NULL(part_fatfs_raw); + TEST_ASSERT_NOT_NULL(part_spiffs); + + readonly_partitions[0] = part_nvs; + readonly_partitions[1] = part_fatfs_wl; + readonly_partitions[2] = part_fatfs_raw; + readonly_partitions[3] = part_spiffs; +} + +#if CONFIG_IDF_TARGET_ESP32 +#define TARGET_CRYPT_CNT_EFUSE ESP_EFUSE_FLASH_CRYPT_CNT +#define TARGET_CRYPT_CNT_WIDTH 7 +#else +#define TARGET_CRYPT_CNT_EFUSE ESP_EFUSE_SPI_BOOT_CRYPT_CNT +#define TARGET_CRYPT_CNT_WIDTH 3 +#endif + +static void example_print_flash_encryption_status(void) +{ + uint32_t flash_crypt_cnt = 0; + esp_efuse_read_field_blob(TARGET_CRYPT_CNT_EFUSE, &flash_crypt_cnt, TARGET_CRYPT_CNT_WIDTH); + printf("FLASH_CRYPT_CNT eFuse value is %" PRIu32 "\n", flash_crypt_cnt); + + esp_flash_enc_mode_t mode = esp_get_flash_encryption_mode(); + if (mode == ESP_FLASH_ENC_MODE_DISABLED) { + printf("Flash encryption feature is disabled\n"); + } else { + printf("Flash encryption feature is enabled in %s mode\n", + mode == ESP_FLASH_ENC_MODE_DEVELOPMENT ? "DEVELOPMENT" : "RELEASE"); + } +} + +void app_main(void) +{ + example_print_flash_encryption_status(); + fill_array_of_readonly_data_partitions(); + unity_run_menu(); +} + +TEST_CASE("Read-only partition - SPI flash API", "[spi_flash]") +{ + esp_err_t err; + char buf[11] = {0}; + const char some_data[] = "0123456789"; + for (int i = 0; i < NUM_OF_READONLY_PARTITIONS; i++) { + const esp_partition_t *part = readonly_partitions[i]; + // Writing to the SPI flash on address overlapping read-only partition shouldn't be possible + // and should return ESP_ERR_NOT_ALLOWED error + err = esp_flash_write(part->flash_chip, some_data, part->address, strlen(some_data)); + ESP_LOGD(TAG, "Writing %u bytes to partition %s at 0x%lx, should return %s and returned %s (0x%x)", + strlen(some_data), part->label, part->address, esp_err_to_name(ESP_ERR_NOT_ALLOWED), esp_err_to_name(err), err); + TEST_ASSERT_EQUAL(ESP_ERR_NOT_ALLOWED, err); + + // Reading the SPI flash on address overlapping read-only partition should be possible without an error + TEST_ESP_OK(esp_flash_read(part->flash_chip, &buf, part->address, strlen(some_data))); + } +} + +TEST_CASE("Read-only partition - Partition API", "[partition]") +{ + esp_err_t err; + // Writing to the partition should not be possible and should return ESP_ERR_NOT_ALLOWED error + const char some_data[] = "0123456789"; + for (int i = 0; i < NUM_OF_READONLY_PARTITIONS; i++) { + err = esp_partition_write(readonly_partitions[i], 0, some_data, strlen(some_data)); + ESP_LOGD(TAG, "esp_partition_write on readonly_partitions[%d] should return %s and returned %s (0x%x)", + i, esp_err_to_name(ESP_ERR_NOT_ALLOWED), esp_err_to_name(err), err); + TEST_ASSERT_EQUAL(ESP_ERR_NOT_ALLOWED, err); + } + + // Reading the partition should be possible without an error + char buf[strlen(some_data)]; + for (int i = 0; i < NUM_OF_READONLY_PARTITIONS; i++) { + err = esp_partition_read(readonly_partitions[i], 0, buf, sizeof(buf)); + TEST_ESP_OK(err); + } +} + +TEST_CASE("Read-only partition - NVS API", "[nvs]") +{ + nvs_handle_t handle; + esp_err_t err; + + err = nvs_flash_init_partition(nvs_partition_name); + TEST_ESP_OK(err); + + // NVS partition flagged as read-only should be possible to open in read-only mode + err = nvs_open_from_partition(nvs_partition_name, "storage", NVS_READONLY, &handle); + TEST_ESP_OK(err); + + // Read test + int32_t i32_val = 0; + err = nvs_get_i32(handle, "i32_key", &i32_val); + TEST_ESP_OK(err); + TEST_ASSERT_EQUAL(-2147483648, i32_val); + nvs_close(handle); + + // NVS partition flagged as read-only shouln't be possible to open in read-write mode + err = nvs_open_from_partition(nvs_partition_name, "storage", NVS_READWRITE, &handle); + TEST_ASSERT_EQUAL(ESP_ERR_NOT_ALLOWED, err); + nvs_close(handle); +} + +void test_c_api_common(const char* base_path) +{ + char hello_txt[64]; + char new_txt[64]; + snprintf(hello_txt, sizeof(hello_txt), "%s%s", base_path, "/hello.txt"); + snprintf(new_txt, sizeof(new_txt), "%s%s", base_path, "/new.txt"); + + FILE *f; + int fd, status; + char buf[CMP_STRING_LEN] = {0}; + + // Test write mode is not possible + f = fopen(hello_txt, "w"); + TEST_ASSERT_NULL(f); + fd = open(hello_txt, O_CREAT|O_WRONLY, 0666); + TEST_ASSERT_EQUAL(-1, fd); + TEST_ASSERT_EQUAL(EROFS, errno); + + f = fopen(hello_txt, "w+"); + TEST_ASSERT_NULL(f); + fd = open(hello_txt, O_CREAT|O_RDWR, 0666); + TEST_ASSERT_EQUAL(-1, fd); + TEST_ASSERT_EQUAL(EROFS, errno); + + f = fopen(hello_txt, "a"); + TEST_ASSERT_NULL(f); + fd = open(hello_txt, O_CREAT|O_WRONLY|O_APPEND, 0666); + TEST_ASSERT_EQUAL(-1, fd); + TEST_ASSERT_EQUAL(EROFS, errno); + + f = fopen(hello_txt, "a+"); + TEST_ASSERT_NULL(f); + fd = open(hello_txt, O_CREAT|O_RDWR|O_APPEND, 0666); + TEST_ASSERT_EQUAL(-1, fd); + TEST_ASSERT_EQUAL(EROFS, errno); + + f = fopen(hello_txt, "r+"); + TEST_ASSERT_NULL(f); + fd = open(hello_txt, O_RDWR); + TEST_ASSERT_EQUAL(-1, fd); + TEST_ASSERT_EQUAL(EROFS, errno); + + fd = creat(new_txt, 0666); // == open(new_txt, O_WRONLY|O_CREAT|O_TRUNC, 0666) + TEST_ASSERT_EQUAL(-1, fd); + TEST_ASSERT_EQUAL(EROFS, errno); + + status = link(hello_txt, new_txt); + TEST_ASSERT_EQUAL(-1, status); + TEST_ASSERT_EQUAL(EROFS, errno); + + status = rename(hello_txt, new_txt); + TEST_ASSERT_EQUAL(-1, status); + TEST_ASSERT_EQUAL(EROFS, errno); + + status = unlink(hello_txt); + TEST_ASSERT_EQUAL(-1, status); + TEST_ASSERT_EQUAL(EROFS, errno); + + status = truncate(hello_txt, 10); + TEST_ASSERT_EQUAL(-1, status); + TEST_ASSERT_EQUAL(EROFS, errno); + + // Test read is still possible + fd = open(hello_txt, O_RDONLY); + TEST_ASSERT_GREATER_THAN(0, fd); + + status = ftruncate(fd, 10); + TEST_ASSERT_EQUAL(-1, status); + TEST_ASSERT_EQUAL(EROFS, errno); + close(fd); + + f = fopen(hello_txt, "r"); + TEST_ASSERT_NOT_NULL(f); + fread(buf, 1, sizeof(buf) - 1, f); + ESP_LOGD(TAG, "Read from file: %s", buf); + TEST_ASSERT_EQUAL(0, strcmp(buf, cmp_string)); + memset(buf, 0, sizeof(buf)); + + char str[] = "Should not be written"; + fseek(f, 0, SEEK_SET); + status = fwrite(str, 1, sizeof(str), f); // Writing should do nothing + TEST_ASSERT_EQUAL(0, status); + TEST_ASSERT_EQUAL(EBADF, errno); + + fread(buf, 1, sizeof(buf) - 1, f); + ESP_LOGD(TAG, "Read from file: %s", buf); + TEST_ASSERT_EQUAL(0, strcmp(buf, cmp_string)); // Test if the file content is still the same + fclose(f); +} + +TEST_CASE("Read-only partition - C file I/O API (using FATFS WL)", "[vfs][fatfs]") +{ + const esp_vfs_fat_mount_config_t mount_config = { + .max_files = 4, + .format_if_mount_failed = false, + .allocation_unit_size = CONFIG_WL_SECTOR_SIZE + }; + + esp_err_t err; + int status; + + err = esp_vfs_fat_spiflash_mount_rw_wl(FATFS_WL_BASE_PATH, fatfs_wl_partition_name, &mount_config, &s_wl_handle); + TEST_ESP_OK(err); + + // FATFS WL itself is read-write capable, but we are restricting it to read-only mode via esp_partition layer + // Opening a file in a write mode on read-only partition is checked in vfs + + test_c_api_common(FATFS_WL_BASE_PATH); + + // Test directories + DIR *dir; + status = mkdir(FATFS_WL_BASE_PATH "/dir1", S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + TEST_ASSERT_EQUAL(-1, status); + TEST_ASSERT_EQUAL(EROFS, errno); + + dir = opendir(FATFS_WL_BASE_PATH "/dir1"); + TEST_ASSERT_NULL(dir); + + status = rmdir(FATFS_WL_BASE_PATH "/dir"); + TEST_ASSERT_EQUAL(-1, status); + TEST_ASSERT_EQUAL(EROFS, errno); + + dir = opendir(FATFS_WL_BASE_PATH "/dir"); + TEST_ASSERT_NOT_NULL(dir); + closedir(dir); + + TEST_ESP_OK(esp_vfs_fat_spiflash_unmount_rw_wl(FATFS_WL_BASE_PATH, s_wl_handle)); +} + +TEST_CASE("Read-only partition - C file I/O API (using FATFS RAW)", "[vfs][fatfs]") +{ + const esp_vfs_fat_mount_config_t mount_config = { + .max_files = 4, + .format_if_mount_failed = false, + .allocation_unit_size = CONFIG_WL_SECTOR_SIZE + }; + + esp_err_t err; + int status; + + err = esp_vfs_fat_spiflash_mount_ro(FATFS_RAW_BASE_PATH, fatfs_raw_partition_name, &mount_config); + TEST_ESP_OK(err); + + // FATFS RAW is read-only itself, but esp_parition read-only adds another layer + // Opening a file in a write mode on read-only partition is checked in vfs + + test_c_api_common(FATFS_RAW_BASE_PATH); + + // Test directories + DIR *dir; + status = mkdir(FATFS_RAW_BASE_PATH "/dir1", S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + TEST_ASSERT_EQUAL(-1, status); + TEST_ASSERT_EQUAL(EROFS, errno); + + dir = opendir(FATFS_RAW_BASE_PATH "/dir1"); + TEST_ASSERT_NULL(dir); + + status = rmdir(FATFS_RAW_BASE_PATH "/dir"); + TEST_ASSERT_EQUAL(-1, status); + TEST_ASSERT_EQUAL(EROFS, errno); + + dir = opendir(FATFS_RAW_BASE_PATH "/dir"); + TEST_ASSERT_NOT_NULL(dir); + closedir(dir); + + TEST_ESP_OK(esp_vfs_fat_spiflash_unmount_ro(FATFS_RAW_BASE_PATH, fatfs_raw_partition_name)); +} + +TEST_CASE("Read-only partition - C file I/O API (using SPIFFS)", "[vfs][spiffs]") +{ + esp_vfs_spiffs_conf_t conf = { + .base_path = SPIFFS_BASE_PATH, + .partition_label = spiffs_partition_name, + .max_files = 5, + .format_if_mount_failed = false + }; + + esp_err_t err; + err = esp_vfs_spiffs_register(&conf); + TEST_ESP_OK(err); + + // SPIFFS is read-write capable, but we are restricting it to read-only mode via esp_partition layer + + test_c_api_common(SPIFFS_BASE_PATH); + + // SPIFFS doesn't support directories + + TEST_ESP_OK(esp_vfs_spiffs_unregister(spiffs_partition_name)); +} diff --git a/tools/test_apps/storage/partition_table_readonly/nvs_data.csv b/tools/test_apps/storage/partition_table_readonly/nvs_data.csv new file mode 100644 index 0000000000..8e0cc35695 --- /dev/null +++ b/tools/test_apps/storage/partition_table_readonly/nvs_data.csv @@ -0,0 +1,13 @@ +# Sample csv file +key,type,encoding,value +storage,namespace,, +u8_key,data,u8,255 +i8_key,data,i8,-128 +u16_key,data,u16,65535 +u32_key,data,u32,4294967295 +i32_key,data,i32,-2147483648 +str_key,data,string,"Lorem ipsum dolor sit amet, consectetur adipiscing elit. +Fusce quis risus justo. +Suspendisse egestas in nisi sit amet auctor. +Pellentesque rhoncus dictum sodales. +In justo erat, viverra at interdum eget, interdum vel dui." diff --git a/tools/test_apps/storage/partition_table_readonly/partitions_example.csv b/tools/test_apps/storage/partition_table_readonly/partitions_example.csv new file mode 100644 index 0000000000..3f34137c61 --- /dev/null +++ b/tools/test_apps/storage/partition_table_readonly/partitions_example.csv @@ -0,0 +1,9 @@ +# ESP-IDF Partition Table +# Name, Type, SubType, Offset, Size, Flags +nvs_ro, data, nvs, , 0x4000, readonly +nvs_key, data, nvs_keys, , 0x1000, encrypted +phy_init, data, phy, , 0x1000, +factory, app, factory, , 0x60000, +fatfs_ro, data, fat, , 528K, readonly +fatfs_raw_ro, data, fat, , 528K, encrypted:readonly +spiffs_ro, data, spiffs, , 256K, readonly diff --git a/tools/test_apps/storage/partition_table_readonly/pytest_partition_table_readonly.py b/tools/test_apps/storage/partition_table_readonly/pytest_partition_table_readonly.py new file mode 100644 index 0000000000..79ce7443cc --- /dev/null +++ b/tools/test_apps/storage/partition_table_readonly/pytest_partition_table_readonly.py @@ -0,0 +1,31 @@ +# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32 +@pytest.mark.esp32c3 +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + [ + 'default', + ], + indirect=True) +def test_partition_table_readonly(dut: Dut) -> None: + dut.run_all_single_board_cases(timeout=120) + + +@pytest.mark.esp32 +@pytest.mark.esp32c3 +@pytest.mark.flash_encryption +@pytest.mark.parametrize( + 'config', + [ + 'encrypted', + ], + indirect=True) +def test_partition_table_readonly_flash_encryption(dut: Dut) -> None: + dut.run_all_single_board_cases(timeout=120) diff --git a/tools/test_apps/storage/partition_table_readonly/sdkconfig.ci.default b/tools/test_apps/storage/partition_table_readonly/sdkconfig.ci.default new file mode 100644 index 0000000000..5520a55072 --- /dev/null +++ b/tools/test_apps/storage/partition_table_readonly/sdkconfig.ci.default @@ -0,0 +1 @@ +CONFIG_SPI_FLASH_DANGEROUS_WRITE_FAILS=y diff --git a/tools/test_apps/storage/partition_table_readonly/sdkconfig.ci.encrypted b/tools/test_apps/storage/partition_table_readonly/sdkconfig.ci.encrypted new file mode 100644 index 0000000000..4cfe55cc81 --- /dev/null +++ b/tools/test_apps/storage/partition_table_readonly/sdkconfig.ci.encrypted @@ -0,0 +1,9 @@ +CONFIG_SPI_FLASH_DANGEROUS_WRITE_FAILS=y +CONFIG_SECURE_FLASH_ENC_ENABLED=y +CONFIG_SECURE_FLASH_ENCRYPTION_MODE_DEVELOPMENT=y +CONFIG_SECURE_BOOT_ALLOW_ROM_BASIC=y +CONFIG_SECURE_BOOT_ALLOW_JTAG=y +CONFIG_SECURE_FLASH_UART_BOOTLOADER_ALLOW_ENC=y +CONFIG_SECURE_FLASH_UART_BOOTLOADER_ALLOW_DEC=y +CONFIG_SECURE_FLASH_UART_BOOTLOADER_ALLOW_CACHE=y +CONFIG_SECURE_FLASH_REQUIRE_ALREADY_ENABLED=y diff --git a/tools/test_apps/storage/partition_table_readonly/sdkconfig.defaults b/tools/test_apps/storage/partition_table_readonly/sdkconfig.defaults new file mode 100644 index 0000000000..9228503563 --- /dev/null +++ b/tools/test_apps/storage/partition_table_readonly/sdkconfig.defaults @@ -0,0 +1,20 @@ +# General options for additional checks +CONFIG_HEAP_POISONING_COMPREHENSIVE=y +CONFIG_COMPILER_WARN_WRITE_STRINGS=y +CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y +CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y +CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y +CONFIG_COMPILER_STACK_CHECK=y +CONFIG_NVS_ASSERT_ERROR_CHECK=y + +# Disable task watchdog since this app uses an interactive menu +CONFIG_ESP_TASK_WDT_INIT=n + +# SPIFFS configuration +CONFIG_SPIFFS_USE_MTIME=n # Disable mtime update as the partition is read-only + +# Custom partition table related +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_example.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions_example.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y