Skip to content

Fix FromBase64Transform.TransformFinalBlock to consistently reset state#124480

Open
Copilot wants to merge 3 commits intomainfrom
copilot/fix-inconsistent-state-clearing
Open

Fix FromBase64Transform.TransformFinalBlock to consistently reset state#124480
Copilot wants to merge 3 commits intomainfrom
copilot/fix-inconsistent-state-clearing

Conversation

Copy link
Contributor

Copilot AI commented Feb 16, 2026

Description

FromBase64Transform.TransformFinalBlock failed to reset internal state when called with empty input (inputCount == 0), while correctly resetting for partial blocks (bytesToTransform < InputBlockSize). This left _inputIndex with stale data, causing subsequent transform operations to fail.

Changes:

  • Added Reset() call before early return at line 192 when inputCount == 0
  • Added test TransformFinalBlock_ShouldResetStateConsistently covering both empty (0 bytes) and partial (1 byte) final block scenarios

Example of the issue:

using FromBase64Transform transform = new();
byte[] partialBlock = [(byte)'A'];

transform.TransformBlock(partialBlock, 0, 1, destination, 0);
transform.TransformFinalBlock(Array.Empty<byte>(), 0, 0);  // Didn't reset

// This would fail due to lingering _inputIndex state
transform.TransformFinalBlock(completeBlock, 0, completeBlock.Length);

All 62 Base64Transform tests pass.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • http://www.microsoft.com:80/pkiops/certs/Microsoft%20Azure%20RSA%20TLS%20Issuing%20CA%2008%20-%20xsign.crt
    • Triggering command: /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet exec --runtimeconfig System.Security.Cryptography.Tests.runtimeconfig.json --depsfile System.Security.Cryptography.Tests.deps.json /home/REDACTED/.nuget/packages/microsoft.dotnet.xunitconsoleREDACTED/2.9.3-beta.26110.116/build/../tools/net/xunit.console.dll System.Security.Cryptography.Tests.dll -xml testResults.xml -nologo -notrait category=OuterLoop -notrait category=failing -method System.Security.Cryptography.Tests.Base64TransformsTests.TransformFinalBlock_ShouldResetStateConsistently x86_64-pc-linux---version -18 GET_AMD64 -DTARGfxr/staticlib/CMakeFiles/libhostfxr.dir/__/__/fx_definition.cpp.o (http block)
    • Triggering command: /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet exec --runtimeconfig System.Security.Cryptography.Tests.runtimeconfig.json --depsfile System.Security.Cryptography.Tests.deps.json /home/REDACTED/.nuget/packages/microsoft.dotnet.xunitconsoleREDACTED/2.9.3-beta.26110.116/build/../tools/net/xunit.console.dll System.Security.Cryptography.Tests.dll -xml testResults.xml -nologo -notrait category=OuterLoop -notrait category=failing -method System.Security.Cryptography.Tests.Base64TransformsTests.TransformFinalBlock_ShouldResetStateConsistently Y_INTERNAL inja e/.dotnet/dotnet/nologo (http block)
    • Triggering command: /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet exec --runtimeconfig System.Security.Cryptography.Tests.runtimeconfig.json --depsfile System.Security.Cryptography.Tests.deps.json /home/REDACTED/.nuget/packages/microsoft.dotnet.xunitconsoleREDACTED/2.9.3-beta.26110.116/build/../tools/net/xunit.console.dll System.Security.Cryptography.Tests.dll -xml testResults.xml -nologo -notrait category=OuterLoop -notrait category=failing -method System.Security.Cryptography.Tests.Base64TransformsTests.TransformFinalBlock_ShouldResetStateConsistently tive inja /bin/sh (http block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

This section details on the original issue you should resolve

<issue_title>FromBase64Transform.TransformFinalBlock clears state inconsistently</issue_title>
<issue_description>Consider the following program:

using System;
using System.Security.Cryptography;

static void Test(int finalBlockSize)
{
    try
    {
        using FromBase64Transform transform = new();
        byte[] destination = new byte[2048];
        byte[] partialBlock = [(byte)'A'];
        byte[] finalBlock = new byte[finalBlockSize];
        finalBlock.AsSpan().Fill((byte)'A');

        transform.TransformBlock(partialBlock, 0, partialBlock.Length, destination, 0);
        byte[] transformed = transform.TransformFinalBlock(finalBlock, 0, finalBlockSize);
        Console.WriteLine($"Test({finalBlockSize}) first transform complete with {transformed.Length}");

        byte[] complete = System.Text.Encoding.UTF8.GetBytes(Convert.ToBase64String("Hello World"u8));
        transformed = transform.TransformFinalBlock(complete, 0, complete.Length);


        Console.WriteLine($"Test({finalBlockSize}) complete with {System.Text.Encoding.UTF8.GetString(transformed)}");
    }
    catch (Exception e)
    {
        Console.WriteLine($"Test({finalBlockSize}) failed with {e}");
    }
}

Test(0);
Test(1);

In both cases, we are first doing a TransformBlock with a partial block of one unit. Next, we call TransformFinalBlock. For the first call to TransformFinalBlock, we vary the input between zero or one input. Regardless of zero or one, this still results in a partial block. The first unit from TransformBlock combined with zero or one more united from TransformFinalBlock is still a partial block (we need four for a complete block).

However, this has observable differences.

In the case of Test(1), a partial block results in a returning an empty buffer and calling Reset:

// Too little data to decode
if (bytesToTransform < InputBlockSize)
{
// reinitialize the transform
Reset();

In the case of Test(0) however, it early exits here:

if (inputCount == 0)
{
return Array.Empty<byte>();
}

This early exit is taken regardless of _inputIndex. So the next use of the FromBase64Transform will still have lingering data in its retained buffer. So the second call to TransformFinalBlock (with a complete block) will observe the left overs from before the first call to TransformBlock

I don't know if this behavior is intentional, or not. There are no tests depending on this behavior though. It seems weird to inconsistently clear state though. I think it would be reasonable for most people to assume that TransformFinalBlock resets it, regardless if the final block was empty or not.</issue_description>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

@github-actions github-actions bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Feb 16, 2026
Copilot AI and others added 2 commits February 16, 2026 21:47
Co-authored-by: vcsjones <361677+vcsjones@users.noreply.github.com>
Co-authored-by: vcsjones <361677+vcsjones@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix FromBase64Transform.TransformFinalBlock inconsistent state clearing Fix FromBase64Transform.TransformFinalBlock to consistently reset state Feb 16, 2026
Copilot AI requested a review from vcsjones February 16, 2026 21:50
@vcsjones vcsjones marked this pull request as ready for review February 16, 2026 22:44
Copilot AI review requested due to automatic review settings February 16, 2026 22:44
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes FromBase64Transform.TransformFinalBlock to always reset internal buffered state when finalizing, including the inputCount == 0 early-return path, preventing subsequent decode operations from being corrupted by stale _inputIndex state.

Changes:

  • Call Reset() when TransformFinalBlock is invoked with empty input (inputCount == 0).
  • Add a regression test ensuring consistent reset behavior for empty (0 bytes) and partial (1 byte) final blocks.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Base64Transforms.cs Resets FromBase64Transform state on the inputCount == 0 early return to avoid stale buffered state.
src/libraries/System.Security.Cryptography/tests/Base64TransformsTests.cs Adds a theory-based regression test validating state reset for both empty and 1-byte final blocks.

@vcsjones vcsjones added area-System.Security and removed area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI labels Feb 16, 2026
@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to this area: @bartonjs, @vcsjones, @dotnet/area-system-security
See info in area-owners.md if you want to be subscribed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FromBase64Transform.TransformFinalBlock clears state inconsistently

2 participants