Repository navigation
Data Corruption in System.Buffers.Text.Base64.EncodeToUtf8 #118143
Description
We started observing intermittent CI failures on tests involving Base64 encoding in late June. This is recognized as a high-risk potential regression lurking in the .NET 10 release, and we need to find the root cause of this in time to resolve it before the .NET 10 RC1 release.
Here are the CI failures associated with this root cause:
- Cryptography.Tests: No PEM encoded data found #117163
2. WasmTestOnChrome-ST-System.Memory.Tests errors #117169 - System.Xml.Tests failing #117614
- System.Security.Cryptography.Rsa.Tests.RSAXml Test Suite Fails with Strings Differ/Containers Differ #117665
Both #117163 and #117169 were reported on June 30, which is the earliest recognized signal of this issue. As was noted in #117665:
That seems... bad?
-f6 ba 82 83 26 0c 39 91 1b 8b 5d b1 8a 0f f3 6a 78 d5 59 a8 0d 64 29 3d d0 0c 35 87 56 00 ...
+f4 0a 80 80 06 00 38 01 00 88 0d 80 88 0f c0 68 08 c0 58 08 00 64 09 00 d0 0c 00 84 06 00 ...
It's like something is clearing every other 6 bits?
- F4BA XOR F40A is 02B0: 0b0000_0010_1011_0000
- 8B5D XOR 880D is 0350: 0b0000_0011_0101_0000
Since base64 is a 6-bit map... I wonder if this is a base64 encoding problem.
This is happening across multiple architectures and operating systems, as well as across different physical machines.
- Machine a00DH2L running Windows-2016Server-10.0.14393-SP0
- Machine a009LPH running Linux-6.8.0-1030-azure-x86_64-with-glibc2.35
- Machine a009QWZ using docker image mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-22.04-helix-webassembly
@eiriktsarpalis conducted a preliminary investigation, reporting:
- The failing tests seem to all share a call to
System.Buffers.Text.Base64.EncodeToUtf8, with that method producing corrupted data - Some of the tests exercise the API through the
System.ConvertAPIs, which only use theBase64class implementation if vectorization is available and applicable
runtime/src/libraries/System.Private.CoreLib/src/System/Convert.cs
Lines 2451 to 2454 in 9df2042
if (Vector128.IsHardwareAccelerated && !insertLineBreaks && bytes.Length >= Base64VectorizationLengthThreshold) { ToBase64CharsLargeNoLineBreaks(bytes, chars, charLengthRequired); } - The relevant implementation in System/Buffers/Text/Base64Helper/Base64EncoderHelper.cs hasn't been updated since October 2024
/cc @artl93 @jeffschwMSFT @marek-safar @lewing for visibility/awareness
Activity
dotnet-policy-service commentedon Jul 28, 2025
Tagging subscribers to this area: @dotnet/area-system-buffers
See info in area-owners.md if you want to be subscribed.
jeffhandley commentedon Jul 28, 2025
With the issue ostensibly narrowed down to
System.Buffers.Text.Base64.EncodeToUtf8, a potential next step is to write a fuzz test against this method with instrumentation that would capture a memory dump when the test fails. Other instrumentation within the failing tests might also yield clues.jeffhandley commentedon Jul 28, 2025
@tannergooding also suggested that once we can repro this, we should run the repro with:
DOTNET_EnableAVX512=0to disable vectorization (edited to reflect this is case-sensitive on non-Windows platforms)If we narrow it down to a vectorization issue, we can use the following to narrow it down further with AVX512 enabled:
DOTNET_ EnableEmbeddedBroadcast=0DOTNET_ EnableEmbeddedMasking=0EgorBo commentedon Jul 28, 2025
A note: it's
DOTNET_EnableAVX512=0since it's case sensitive on non-windows platformsjeffhandley commentedon Jul 28, 2025
I've reviewed all of the failures and have only found occurrences on Amd64 hardware. There was one that appeared to be Arm64, but it looks to be a false-positive match on the failure.
bartonjs commentedon Jul 28, 2025
I don't know if it's useful for differential diagnosis, but in case it is, I've never seen a repro on this CPU:
EgorBo commentedon Jul 28, 2025
Interesting, seems to be some old CPU without AVX2 (AVX only).
bartonjs commentedon Jul 28, 2025
@EgorBo Might be old, but it does what I need it to 😄. Since there seems to be speculation that it has to do with JIT optimizations and/or intrinsics-enhanced codepaths, I figured I'd share a case where the corruption hasn't ever appeared.
EgorBo commentedon Jul 28, 2025
I tried to optimistically repro it by comparing
Base64.EncodeToUtf8output with a naive version generated by GPT for random inputs - was not able to reproduce any mismatch (also tested various EnableAVX knobs). https://gist.github.com/EgorBo/1bf17a02c94d587917d81789d53af847Probably also makes sense to try it on inputs created with VirtualAlloc to detect cases when it reads out of bounds (if it does).
EgorBo commentedon Jul 28, 2025
I think we need a dump and/or more info about input size it failed on
EgorBo commentedon Jul 29, 2025
Hm.. probably found something, this repro fails on Checked runtime built from source (main) with:
but only with AVX512
1 remaining item
dotnet-policy-service commentedon Jul 29, 2025
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.
jkotas commentedon Jul 29, 2025
This points to a JIT bug. cc @dotnet/jit-contrib This is .NET 10 blocker.
EgorBo commentedon Jul 29, 2025
Bisected to #116983 cc @tannergooding
tannergooding commentedon Jul 29, 2025
It's looking like this is hit during Tier1 when we rejit
Test:Enabling
MinOptsfor this method in particular causes it to no longer repro. It appears as though we're inlining aSequenceEqualsand theBase64Helper.EncodeTocall, where we previously didn't. The latter is what is causing the problem, in particular the inlining ofAvx512EncodeandAvx2Encodesimultaneously causes the issue.That indicates this might be VN or CSE related.
tannergooding commentedon Jul 29, 2025
Not morph or constant folding related.
tannergooding commentedon Jul 29, 2025
Issue is somewhere in the handling ofCompareGreaterThanMaskforTYP_SIMD32Actually nevermind, that alone isn't sufficient for catching the issue.
tannergooding commentedon Jul 29, 2025
Explanation of the issue and the fix is here: #118154