[SL-UP] Fix NVM3 Key leak on reboot (#464) (#39671)

diff --git a/examples/platform/silabs/BaseApplication.cpp b/examples/platform/silabs/BaseApplication.cpp
index a662f35..a6f24c5 100644
--- a/examples/platform/silabs/BaseApplication.cpp
+++ b/examples/platform/silabs/BaseApplication.cpp
@@ -882,10 +882,16 @@
 void BaseApplication::DoProvisioningReset()
 {
     PlatformMgr().ScheduleWork([](intptr_t) {
+        // Force the KeyMap update to make sure nvm3 is updated before anything happens.
+        // If the device reboots before the timer update happens, "shadow" keys are left in nvm3 causing a reduction of the
+        // overall available nvm3 - similar to a memory leak.
+        // FactoryResetThreadStack forces a reboot which was causing a memory loss.
+        chip::DeviceLayer::PersistedStorage::KeyValueStoreMgrImpl().ForceKeyMapSave();
+
 #if CHIP_DEVICE_CONFIG_ENABLE_THREAD
         ConfigurationManagerImpl::GetDefaultInstance().ClearThreadStack();
         ThreadStackMgrImpl().FactoryResetThreadStack();
-        ThreadStackMgr().InitThreadStack();
+        // Triggers a reboot - nothing gets executed after this
 #endif // CHIP_DEVICE_CONFIG_ENABLE_THREAD
 
 #if CHIP_DEVICE_CONFIG_ENABLE_WIFI_STATION
diff --git a/src/platform/silabs/KeyValueStoreManagerImpl.cpp b/src/platform/silabs/KeyValueStoreManagerImpl.cpp
index 34d8838..9ea9e36 100644
--- a/src/platform/silabs/KeyValueStoreManagerImpl.cpp
+++ b/src/platform/silabs/KeyValueStoreManagerImpl.cpp
@@ -22,6 +22,7 @@
  */
 
 #include "MigrationManager.h"
+#include <cmsis_os2.h>
 #include <crypto/CHIPCryptoPAL.h>
 #include <platform/CHIPDeviceLayer.h>
 #include <platform/KeyValueStoreManager.h>
@@ -40,29 +41,38 @@
 namespace DeviceLayer {
 namespace PersistedStorage {
 
-KeyValueStoreManagerImpl KeyValueStoreManagerImpl::sInstance;
+namespace {
+
 uint16_t mKvsKeyMap[KeyValueStoreManagerImpl::kMaxEntries] = { 0 };
 
+} // namespace
+
+KeyValueStoreManagerImpl KeyValueStoreManagerImpl::sInstance;
+
 CHIP_ERROR KeyValueStoreManagerImpl::Init(void)
 {
-    CHIP_ERROR err;
-    err = SilabsConfig::Init();
-    SuccessOrExit(err);
+    ReturnErrorOnFailure(SilabsConfig::Init());
 
     Silabs::MigrationManager::GetMigrationInstance().applyMigrations();
 
     memset(mKvsKeyMap, 0, sizeof(mKvsKeyMap));
-    size_t outLen;
-    err = SilabsConfig::ReadConfigValueBin(SilabsConfig::kConfigKey_KvsStringKeyMap, reinterpret_cast<uint8_t *>(mKvsKeyMap),
-                                           sizeof(mKvsKeyMap), outLen);
-
-    if (err == CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND) // Initial boot
+    size_t outLen    = 0;
+    CHIP_ERROR error = SilabsConfig::ReadConfigValueBin(SilabsConfig::kConfigKey_KvsStringKeyMap,
+                                                        reinterpret_cast<uint8_t *>(mKvsKeyMap), sizeof(mKvsKeyMap), outLen);
+    if (error == CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND) // Initial boot
     {
-        err = CHIP_NO_ERROR;
+        error = CHIP_NO_ERROR;
+    }
+    ReturnErrorOnFailure(error);
+
+    constexpr osThreadAttr_t attr = { .name = "KvsKeyMapCleanupTask", .stack_size = 512 /* bytes */, .priority = osPriorityLow7 };
+    if (osThreadNew(KvsKeyMapCleanup, nullptr, &attr) == nullptr)
+    {
+        // We don't need to crash and force a reboot. we will retry at the next reboot
+        ChipLogError(DeviceLayer, "Failed to create KvsCleanupTask");
     }
 
-exit:
-    return err;
+    return CHIP_NO_ERROR;
 }
 
 bool KeyValueStoreManagerImpl::IsValidKvsNvm3Key(uint32_t nvm3Key) const
@@ -109,7 +119,15 @@
             // Collision prevention
             // Read the data from NVM3 it should be prefixed by the kvsString
             // else we will look for another matching hash in the map
-            SilabsConfig::ReadConfigValueBin(tempNvm3key, reinterpret_cast<uint8_t *>(strPrefix), length, readCount, 0);
+
+            CHIP_ERROR err =
+                SilabsConfig::ReadConfigValueBin(tempNvm3key, reinterpret_cast<uint8_t *>(strPrefix), length, readCount, 0);
+            if (err == CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND)
+            {
+                // No steps to be taken, clean up will be done at init
+                ChipLogError(DeviceLayer, "Key Hash with no associated NVM3 entry - potential Shadow key detected");
+            }
+
             if (strcmp(key, strPrefix) == 0)
             {
                 // String matches we have confirmed the hash pointed us the right key data
@@ -299,6 +317,34 @@
     }
 }
 
+void KeyValueStoreManagerImpl::KvsKeyMapCleanup(void * argument)
+{
+    bool requireKvsKeyMapSave = false;
+
+    for (uint16_t key = 0; key < KeyValueStoreManagerImpl::kMaxEntries; key++)
+    {
+        // Only check the keys that should have a NVM entry and
+        // we don't need to take a mutex - protection is done by the underlying nvm3 APIs called in ConfigValueExists
+        if (mKvsKeyMap[key] != 0 && !SilabsConfig::ConfigValueExists(CONVERT_KEYMAP_INDEX_TO_NVM3KEY(key)))
+        {
+            // We have found a shadow key (key with no NVM entry)
+            // Delete unused key to free up space
+            mKvsKeyMap[key]      = 0;
+            requireKvsKeyMapSave = true;
+        }
+    }
+
+    if (requireKvsKeyMapSave)
+    {
+        PlatformMgr().LockChipStack();
+        KeyValueStoreMgrImpl().ForceKeyMapSave();
+        PlatformMgr().UnlockChipStack();
+    }
+
+    // Single shot task at boot - delete the task when the clean up is complete
+    osThreadExit();
+}
+
 } // namespace PersistedStorage
 
 namespace Silabs {
diff --git a/src/platform/silabs/KeyValueStoreManagerImpl.h b/src/platform/silabs/KeyValueStoreManagerImpl.h
index c17fad8..2cf20a7 100644
--- a/src/platform/silabs/KeyValueStoreManagerImpl.h
+++ b/src/platform/silabs/KeyValueStoreManagerImpl.h
@@ -46,8 +46,19 @@
     static void KvsMapMigration();
 
 private:
+    static KeyValueStoreManagerImpl sInstance;
+
     static void OnScheduledKeyMapSave(System::Layer * systemLayer, void * appState);
 
+    /**
+     * @brief Cleans up unused keys in the key-value store.
+     *
+     * This function iterates over the key map and removes shadow keys (keys that
+     * no longer have corresponding entries in NVM). It ensures that the key map
+     * remains consistent and frees up space for new entries.
+     */
+    static void KvsKeyMapCleanup(void * argument);
+
     void ScheduleKeyMapSave(void);
     bool IsValidKvsNvm3Key(const uint32_t nvm3Key) const;
     uint16_t hashKvsKeyString(const char * key) const;
@@ -56,8 +67,6 @@
     //  ===== Members for internal use by the following friends.
     friend KeyValueStoreManager & KeyValueStoreMgr();
     friend KeyValueStoreManagerImpl & KeyValueStoreMgrImpl();
-
-    static KeyValueStoreManagerImpl sInstance;
 };
 
 /**
@@ -75,7 +84,7 @@
  * Returns the platform-specific implementation of the KeyValueStoreManager singleton object.
  *
  * Chip applications can use this to gain access to features of the KeyValueStoreManager
- * that are specific to the ESP32 platform.
+ * that are specific to the Silabs platform.
  */
 inline KeyValueStoreManagerImpl & KeyValueStoreMgrImpl(void)
 {