Shared Mutable State

ID

csharp.shared_mutable_state

Severity

low

Remediation Complexity

medium

Remediation Risk

medium

Remediation Effort

medium

Resource

Synchronization

Language

CSharp

Tags

ASVS50:v15.4.1, CWE:362, CWE:567, NIST.SP.800-53, PCI-DSS:6.5.6

Description

Detects shared mutable state — static fields and properties, and fields of a class the application registers as a singleton — that are written outside initialization code. State that lives once per process is accessed and modified by concurrent requests, creating potential race conditions.

When a field is assigned a value read from a method parameter (or a setter’s value), concurrent requests hand one caller’s data to another, and that finding is raised to high. When the field is a counter or a collection written concurrently, updates are lost and the collection can corrupt.

One issue is reported per field, at its worst write.

Rationale

ASP.NET Core creates a service registered with AddSingleton<T>(), AddSingleton<TService, TImplementation>() or AddHostedService<T>() once, and hands the same instance to every request. A static field is shared the same way. Controllers, and services registered with AddScoped or AddTransient, are created per request and are not reported.

A counter that loses updates:

public class HitCounter   // services.AddSingleton<HitCounter>()
{
    private int _served;

    public int Serve()
    {
        _served++;   // read-modify-write is not atomic
        return _served;
    }
}

A dictionary written by every request:

public class InMemoryOrderStore : IOrderStore   // services.AddSingleton<IOrderStore, InMemoryOrderStore>()
{
    private readonly Dictionary<string, Order> _orders = new();

    public void Save(Order order) => _orders[order.Number] = order;   // Dictionary is not safe for concurrent writes
}

Remediation

  1. Eliminate Shared State: keep request data in parameters and return values, or in a scoped service.

  2. Use Thread-Safe Types: counters updated through Interlocked.Increment, ConcurrentDictionary for caches.

  3. Synchronize Access: guard every access with the same lock:

    private readonly object _gate = new();
    private readonly Dictionary<string, string> _cache = new();
    
    public void Update(string key, string value)
    {
        lock (_gate)
        {
            _cache[key] = value;
        }
    }

Not Reported

  • A thread-safe type (ConcurrentDictionary, ImmutableList, Lazy<T>, ThreadLocal<T>…), declared or constructed, and a [ThreadStatic] field.

  • A write inside lock (…) { }, in a [MethodImpl(MethodImplOptions.Synchronized)] method, or after a lock is taken on a field (await _semaphore.WaitAsync(), Monitor.Enter); an Interlocked update.

  • An instance field of a controller, or of a service that is not registered as a singleton.

  • A lazy initialisation, if (x == null) x = … or x ??= …, whose value is not a parameter.

  • A write in a constructor, a static constructor, an init accessor or a host start-up callback (IHostedService.StartAsync), and in the loop of a BackgroundService.ExecuteAsync, which the host starts once, or a private helper only that loop calls.

  • A member whose only write drops a cached object (_cachedRoot = null): one reference write, after which the next reader rebuilds it.

  • An options class the application binds with Configure<T>.

  • A private helper that only constructors call.

  • A call named like a mutator on a field that is not a collection (_cache.Set(…), KeyFormat.Replace(…)).

  • A test source.

© 2026 Xygeni. All rights reserved