From 970b54962faeeb99c6fff499c01c1e458c9016df Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Tue, 14 Apr 2026 13:27:29 +0530 Subject: [PATCH] fix(nvs_flash): zeroize XTS contexts when encrypted partition is destroyed NVSEncryptedPartition held two XTS_CONTEXT members (mEctxt, mDctxt) for encryption / decryption. Their AES round keys are derived from the NVS encryption key (HMAC-derived or plaintext from nvs_keys partition) and therefore are sensitive secrets. The destructor was empty, so when the NVS encrypted partition object was destroyed -- on nvs_flash_deinit_partition(), on initialization errors, and on any other teardown path -- the XTS round keys were left in DRAM until the freed object's memory happened to be overwritten by a later allocation. A subsequent stack/heap leak primitive would recover the AES key from those bytes. Fix: * Initialize both XTS contexts in the constructor so the destructor's free path is always safe (previously xts_init was only called in init(); destruction before init() would have run xts_free on an uninitialized struct). * Provide a real destructor that calls XTS_FUNC(xts_free) on both contexts, which performs mbedtls_platform_zeroize / esp_aes_xts free semantics on the underlying AES contexts. --- .../nvs_flash/src/nvs_encrypted_partition.cpp | 20 +++++++++++++++++-- .../nvs_flash/src/nvs_encrypted_partition.hpp | 2 +- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/components/nvs_flash/src/nvs_encrypted_partition.cpp b/components/nvs_flash/src/nvs_encrypted_partition.cpp index c108fe30ad3..053a3ee1f0b 100644 --- a/components/nvs_flash/src/nvs_encrypted_partition.cpp +++ b/components/nvs_flash/src/nvs_encrypted_partition.cpp @@ -13,12 +13,28 @@ namespace nvs { #ifdef CONFIG_NVS_BDL_STACK NVSEncryptedPartition::NVSEncryptedPartition(const char* label, const esp_blockdev_handle_t bdl, const bool managed_bdl) - : NVSPartition(label, bdl, managed_bdl) { } + : NVSPartition(label, bdl, managed_bdl) +{ + XTS_FUNC(xts_init)(&mEctxt); + XTS_FUNC(xts_init)(&mDctxt); +} #else NVSEncryptedPartition::NVSEncryptedPartition(const esp_partition_t *partition) - : NVSPartition(partition) { } + : NVSPartition(partition) +{ + XTS_FUNC(xts_init)(&mEctxt); + XTS_FUNC(xts_init)(&mDctxt); +} #endif // CONFIG_NVS_BDL_STACK +NVSEncryptedPartition::~NVSEncryptedPartition() +{ + /* Wipe AES round keys derived from the NVS encryption key so they are not + * left in DRAM after the partition object is destroyed. */ + XTS_FUNC(xts_free)(&mEctxt); + XTS_FUNC(xts_free)(&mDctxt); +} + esp_err_t NVSEncryptedPartition::init(nvs_sec_cfg_t* cfg) { uint8_t* eky = reinterpret_cast(cfg); diff --git a/components/nvs_flash/src/nvs_encrypted_partition.hpp b/components/nvs_flash/src/nvs_encrypted_partition.hpp index ffdea250aea..24c98d5359a 100644 --- a/components/nvs_flash/src/nvs_encrypted_partition.hpp +++ b/components/nvs_flash/src/nvs_encrypted_partition.hpp @@ -23,7 +23,7 @@ public: NVSEncryptedPartition(const esp_partition_t *partition); #endif - virtual ~NVSEncryptedPartition() { } + virtual ~NVSEncryptedPartition(); /** * Initializes the AES encryption components with the provided configuration.