VYPR
High severity7.5NVD Advisory· Published Mar 5, 2025· Updated Apr 15, 2026

CVE-2025-27513

CVE-2025-27513

Description

OpenTelemetry dotnet is a dotnet telemetry framework. A vulnerability in OpenTelemetry.Api package 1.10.0 to 1.11.1 could cause a Denial of Service (DoS) when a tracestate and traceparent header is received. Even if an application does not explicitly use trace context propagation, receiving these headers can still trigger high CPU usage. This issue impacts any application accessible over the web or backend services that process HTTP requests containing a tracestate header. Application may experience excessive resource consumption, leading to increased latency, degraded performance, or downtime. This vulnerability is fixed in 1.11.2.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
OpenTelemetry.ApiNuGet
>= 1.11.0, < 1.11.21.11.2

Patches

2
1b555c120141

Revert TraceContextPropagator performance refactor from 5749 (#6161)

2 files changed · +28 112
  • src/OpenTelemetry.Api/CHANGELOG.md+4 0 modified
    @@ -6,6 +6,10 @@ Notes](../../RELEASENOTES.md).
     
     ## Unreleased
     
    +* Revert optimize performance of `TraceContextPropagator.Extract` introduced
    +  in #5749 to resolve #6158.
    +  ([#6161](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6161))
    +
     ## 1.11.1
     
     Released 2025-Jan-22
    
  • src/OpenTelemetry.Api/Context/Propagation/TraceContextPropagator.cs+24 112 modified
    @@ -1,9 +1,9 @@
     // Copyright The OpenTelemetry Authors
     // SPDX-License-Identifier: Apache-2.0
     
    -using System.Buffers;
     using System.Diagnostics;
     using System.Runtime.CompilerServices;
    +using System.Text;
     using OpenTelemetry.Internal;
     
     namespace OpenTelemetry.Context.Propagation;
    @@ -76,7 +76,7 @@ public override PropagationContext Extract<T>(PropagationContext context, T carr
                 var tracestateCollection = getter(carrier, TraceState);
                 if (tracestateCollection?.Any() ?? false)
                 {
    -                TryExtractTracestate(tracestateCollection, out tracestate);
    +                TryExtractTracestate(tracestateCollection.ToArray(), out tracestate);
                 }
     
                 return new PropagationContext(
    @@ -220,37 +220,31 @@ internal static bool TryExtractTraceparent(string traceparent, out ActivityTrace
             return true;
         }
     
    -    internal static bool TryExtractTracestate(IEnumerable<string> tracestateCollection, out string tracestateResult)
    +    internal static bool TryExtractTracestate(string[] tracestateCollection, out string tracestateResult)
         {
             tracestateResult = string.Empty;
     
    -        char[]? rentedArray = null;
    -        Span<char> traceStateBuffer = stackalloc char[128]; // 256B
    -        Span<char> keyLookupBuffer = stackalloc char[96]; // 192B (3x32 keys)
    -        int keys = 0;
    -        int charsWritten = 0;
    -
    -        try
    +        if (tracestateCollection != null)
             {
    -            foreach (var tracestateItem in tracestateCollection)
    +            var keySet = new HashSet<string>();
    +            var result = new StringBuilder();
    +            for (int i = 0; i < tracestateCollection.Length; ++i)
                 {
    -                var tracestate = tracestateItem.AsSpan();
    -                int position = 0;
    -
    -                while (position < tracestate.Length)
    +                var tracestate = tracestateCollection[i].AsSpan();
    +                int begin = 0;
    +                while (begin < tracestate.Length)
                     {
    -                    int length = tracestate.Slice(position).IndexOf(',');
    +                    int length = tracestate.Slice(begin).IndexOf(',');
                         ReadOnlySpan<char> listMember;
    -
                         if (length != -1)
                         {
    -                        listMember = tracestate.Slice(position, length).Trim();
    -                        position += length + 1;
    +                        listMember = tracestate.Slice(begin, length).Trim();
    +                        begin += length + 1;
                         }
                         else
                         {
    -                        listMember = tracestate.Slice(position).Trim();
    -                        position = tracestate.Length;
    +                        listMember = tracestate.Slice(begin).Trim();
    +                        begin = tracestate.Length;
                         }
     
                         // https://github.com/w3c/trace-context/blob/master/spec/20-http_request_header_format.md#tracestate-header-field-values
    @@ -261,7 +255,7 @@ internal static bool TryExtractTracestate(IEnumerable<string> tracestateCollecti
                             continue;
                         }
     
    -                    if (keys >= 32)
    +                    if (keySet.Count >= 32)
                         {
                             // https://github.com/w3c/trace-context/blob/master/spec/20-http_request_header_format.md#list
                             // test_tracestate_member_count_limit
    @@ -292,107 +286,25 @@ internal static bool TryExtractTracestate(IEnumerable<string> tracestateCollecti
                         }
     
                         // ValidateKey() call above has ensured the key does not contain upper case letters.
    -
    -                    var duplicationCheckLength = Math.Min(key.Length, 3);
    -
    -                    if (keys > 0)
    -                    {
    -                        // Fast path check of first three chars for potential duplicated keys
    -                        var potentialMatchingKeyPosition = 1;
    -                        var found = false;
    -                        for (int i = 0; i < keys * 3; i += 3)
    -                        {
    -                            if (keyLookupBuffer.Slice(i, duplicationCheckLength).SequenceEqual(key.Slice(0, duplicationCheckLength)))
    -                            {
    -                                found = true;
    -                                break;
    -                            }
    -
    -                            potentialMatchingKeyPosition++;
    -                        }
    -
    -                        // If the fast check has found a possible duplicate, we need to do a full check
    -                        if (found)
    -                        {
    -                            var bufferToCompare = traceStateBuffer.Slice(0, charsWritten);
    -
    -                            // We know which key is the first possible duplicate, so skip to that key
    -                            // by slicing to the position after the appropriate comma.
    -                            for (int i = 1; i < potentialMatchingKeyPosition; i++)
    -                            {
    -                                var commaIndex = bufferToCompare.IndexOf(',');
    -
    -                                if (commaIndex > -1)
    -                                {
    -                                    bufferToCompare.Slice(commaIndex);
    -                                }
    -                            }
    -
    -                            int existingIndex = -1;
    -                            while ((existingIndex = bufferToCompare.IndexOf(key)) > -1)
    -                            {
    -                                if ((existingIndex > 0 && bufferToCompare[existingIndex - 1] != ',') || bufferToCompare[existingIndex + key.Length] != '=')
    -                                {
    -                                    continue; // this is not a key
    -                                }
    -
    -                                return false; // test_tracestate_duplicated_keys
    -                            }
    -                        }
    -                    }
    -
    -                    // Store up to the first three characters of the key for use in the duplicate lookup fast path
    -                    var startKeyLookupIndex = keys > 0 ? keys * 3 : 0;
    -                    key.Slice(0, duplicationCheckLength).CopyTo(keyLookupBuffer.Slice(startKeyLookupIndex));
    -
    -                    // Check we have capacity to write the key and value
    -                    var requiredCapacity = charsWritten > 0 ? listMember.Length + 1 : listMember.Length;
    -
    -                    while (charsWritten + requiredCapacity > traceStateBuffer.Length)
    +                    if (!keySet.Add(key.ToString()))
                         {
    -                        GrowBuffer(ref rentedArray, ref traceStateBuffer);
    +                        // test_tracestate_duplicated_keys
    +                        return false;
                         }
     
    -                    if (charsWritten > 0)
    +                    if (result.Length > 0)
                         {
    -                        traceStateBuffer[charsWritten++] = ',';
    +                        result.Append(',');
                         }
     
    -                    listMember.CopyTo(traceStateBuffer.Slice(charsWritten));
    -                    charsWritten += listMember.Length;
    -
    -                    keys++;
    +                    result.Append(listMember.ToString());
                     }
                 }
     
    -            tracestateResult = traceStateBuffer.Slice(0, charsWritten).ToString();
    -
    -            return true;
    +            tracestateResult = result.ToString();
             }
    -        finally
    -        {
    -            if (rentedArray is not null)
    -            {
    -                ArrayPool<char>.Shared.Return(rentedArray);
    -                rentedArray = null;
    -            }
    -        }
    -
    -        [MethodImpl(MethodImplOptions.AggressiveInlining)]
    -        static void GrowBuffer(ref char[]? array, ref Span<char> buffer)
    -        {
    -            var newBuffer = ArrayPool<char>.Shared.Rent(buffer.Length * 2);
     
    -            buffer.CopyTo(newBuffer.AsSpan());
    -
    -            if (array is not null)
    -            {
    -                ArrayPool<char>.Shared.Return(array);
    -            }
    -
    -            array = newBuffer;
    -            buffer = array.AsSpan();
    -        }
    +        return true;
         }
     
         private static byte HexCharToByte(char c)
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.