diff --git a/CHANGELOG.md b/CHANGELOG.md index 89f909aa..8b0b445e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,17 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [0.48.12] - 2026-05-15 + +### Fixed +- **DiskHealthService (CQ-007)** — WQL ASSOCIATORS OF query now escapes single + quotes in objectId, preventing potential WQL injection. +- **OperationLockService (CQ-008)** — removed redundant lock object; TryAcquire + and Release now use ConcurrentDictionary atomic TryAdd/TryRemove directly. +- **PingMonitorService (CQ-015)** — CancellationTokenSource only disposed if the + background loop actually completed, preventing ObjectDisposedException in + still-running pump tasks. + ## [0.48.11] - 2026-05-15 ### Fixed diff --git a/SysManager/SysManager/Services/DiskHealthService.cs b/SysManager/SysManager/Services/DiskHealthService.cs index 4fd3aae1..39d2dac7 100644 --- a/SysManager/SysManager/Services/DiskHealthService.cs +++ b/SysManager/SysManager/Services/DiskHealthService.cs @@ -69,7 +69,7 @@ private static void EnrichWithReliability(ManagementScope scope, string objectId try { // Escape quotes & backslashes for the WQL literal. - var safeId = objectId.Replace("\\", "\\\\").Replace("\"", "\\\""); + var safeId = objectId.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("'", "\\'"); var query = new ObjectQuery( $"ASSOCIATORS OF {{MSFT_PhysicalDisk.ObjectId=\"{safeId}\"}} WHERE AssocClass=MSFT_PhysicalDiskToStorageReliabilityCounter"); using var searcher = new ManagementObjectSearcher(scope, query); diff --git a/SysManager/SysManager/Services/OperationLockService.cs b/SysManager/SysManager/Services/OperationLockService.cs index fab9e21f..3eba835e 100644 --- a/SysManager/SysManager/Services/OperationLockService.cs +++ b/SysManager/SysManager/Services/OperationLockService.cs @@ -34,7 +34,6 @@ public sealed partial class OperationLockService : ObservableObject public static OperationLockService Instance => _instance.Value; private readonly ConcurrentDictionary _active = new(); - private readonly object _lock = new(); private OperationLockService() { } @@ -47,17 +46,13 @@ private OperationLockService() { } /// A disposable lock handle, or null if the category is busy. public OperationHandle? TryAcquire(OperationCategory category, string operationName) { - lock (_lock) - { - if (_active.ContainsKey(category)) - return null; - - var info = new OperationInfo(operationName, DateTime.UtcNow); - _active[category] = info; - OnPropertyChanged(nameof(ActiveOperations)); - OnPropertyChanged(nameof(HasActiveOperations)); - return new OperationHandle(this, category); - } + var info = new OperationInfo(operationName, DateTime.UtcNow); + if (!_active.TryAdd(category, info)) + return null; + + OnPropertyChanged(nameof(ActiveOperations)); + OnPropertyChanged(nameof(HasActiveOperations)); + return new OperationHandle(this, category); } /// @@ -84,12 +79,9 @@ private OperationLockService() { } private void Release(OperationCategory category) { - lock (_lock) - { - _active.TryRemove(category, out _); - OnPropertyChanged(nameof(ActiveOperations)); - OnPropertyChanged(nameof(HasActiveOperations)); - } + _active.TryRemove(category, out _); + OnPropertyChanged(nameof(ActiveOperations)); + OnPropertyChanged(nameof(HasActiveOperations)); } /// diff --git a/SysManager/SysManager/Services/PingMonitorService.cs b/SysManager/SysManager/Services/PingMonitorService.cs index 17441332..4c73492e 100644 --- a/SysManager/SysManager/Services/PingMonitorService.cs +++ b/SysManager/SysManager/Services/PingMonitorService.cs @@ -56,7 +56,11 @@ public void Stop() try { _loop?.Wait(1500); } catch (AggregateException) { /* task cancellation or faulted — expected during stop */ } catch (ObjectDisposedException) { /* task already cleaned up */ } - _cts?.Dispose(); + // Only dispose CTS if the loop actually completed; otherwise the + // background task still holds a reference to the token and would + // throw ObjectDisposedException on next cancellation check. + if (_loop is { IsCompleted: true }) + _cts?.Dispose(); _cts = null; _loop = null; }