Skip to content

FromBase64Transform.TransformFinalBlock clears state inconsistently #122809

@vcsjones

Description

@vcsjones

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.

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions