From fb5f191b36181f3a7700fcab26214beff0288ce1 Mon Sep 17 00:00:00 2001 From: HetCreep Date: Sun, 21 Jun 2026 17:13:51 +0700 Subject: [PATCH] Enumerate a config snapshot under the lock (#1357) ConfigFile documents its public methods as thread-safe, but GetEnumerator returned the live Dictionary enumerator with no lock, so a concurrent Bind/Reload could invalidate it mid-foreach. Snapshot under _ioLock instead. --- BepInEx.Core/Configuration/ConfigFile.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/BepInEx.Core/Configuration/ConfigFile.cs b/BepInEx.Core/Configuration/ConfigFile.cs index 8f67ef7..dfa756b 100644 --- a/BepInEx.Core/Configuration/ConfigFile.cs +++ b/BepInEx.Core/Configuration/ConfigFile.cs @@ -92,9 +92,12 @@ public class ConfigFile : IDictionary public ConfigEntryBase this[string section, string key] => this[new ConfigDefinition(section, key)]; /// - public IEnumerator> GetEnumerator() => - // We can't really do a read lock for this - Entries.GetEnumerator(); + public IEnumerator> GetEnumerator() + { + // Enumerate a snapshot taken under the lock so a concurrent mutation cannot invalidate the iterator. + lock (_ioLock) + return Entries.ToList().GetEnumerator(); + } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();