Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Dec 31, 2025

main PR

Description

This PR fixes two bugs in FromBase64Transform:

1. CryptoPool rental sizing bug: TransformBlock and TransformFinalBlock rent from CryptoPool using inputCount instead of bytesToTransform. When retained bytes exist from a previous partial block (_inputIndex > 0) and the new input size is near a power of two, the rented buffer is undersized.

// Before: rents only for new input, ignoring retained bytes
transformBuffer = transformBufferArray = CryptoPool.Rent(inputCount);

// After: rents for total bytes (retained + new input)
transformBuffer = transformBufferArray = CryptoPool.Rent(bytesToTransform);

2. Output buffer too small handling (#122807): When Base64.DecodeFromUtf8 returns OperationStatus.DestinationTooSmall, the code was hitting a Debug.Assert (fails in debug builds) and then throwing FormatException (incorrect). The fix properly handles this case by throwing ArgumentOutOfRangeException("outputBuffer") instead.

Customer Impact

  • ArgumentException: Destination is too short when using FromBase64Transform with partial blocks followed by inputs near power-of-two sizes.
  • Debug assert failures and incorrect FormatException (instead of ArgumentOutOfRangeException) when the output buffer is too small.

Regression

Unknown. Code has been unchanged since 2023.

Testing

  • Added TransformBlock_PartialBlockFollowedByPowerOfTwoInput_DoesNotThrow
  • Added TransformFinalBlock_PartialBlockFollowedByPowerOfTwoInput_DoesNotThrow
  • Added TransformBlock_OutputBufferTooSmall_ThrowsArgumentOutOfRangeException
  • All existing Base64TransformsTests pass

Risk

Low. The changes are minimal:

  • Two-line fix changing inputCount to bytesToTransform in the CryptoPool.Rent calls (both variables are already computed and used in the same methods)
  • Four-line addition to handle OperationStatus.DestinationTooSmall explicitly before the existing InvalidData handling

Package authoring no longer needed in .NET 9

IMPORTANT: Starting with .NET 9, you no longer need to edit a NuGet package's csproj to enable building and bump the version.
Keep in mind that we still need package authoring in .NET 8 and older versions.

Original prompt

This section details on the original issue you should resolve

<issue_title>FromBase64Transform incorrectly rents from CryptoPool (ArrayPool)</issue_title>
<issue_description>### Description

FromBase64Transform, when given a partial block, will retain the block and keep it around for the next transform so that it can transform a complete block. That happens here:

// Too little data to decode: save data to _inputBuffer, so it can be transformed later
if (bytesToTransform < InputBlockSize)

On the next call to TransformBlock, it will take the retained input and combine it with the input to the transformed data. In order to do this, it creates a contiguous buffer and combines them. It does this on the stack if the buffer is small enough, otherwise it rents from CryptoPool (a veneer over ArrayPool).

However, when it rents from the CryptoPool, it does not account for the retained buffer. This means that if the second call to TransformBlock is within three bytes of a power of two, the rented buffer won't be large enough to complete the copy. That rent happens here:

transformBuffer = transformBufferArray = CryptoPool.Rent(inputCount);

Note that it is renting inputCount, not bytesToTransform.

This likely "works" in a lot of scenarios because usually the amount rented from the ArrayPool is oversized. This can only be hit when:

  1. There are N number of byte retained in _inputIndex
  2. The next transform is within N number of bytes of a power of two that hits the rental size boundary.

Reproduction Steps

using System;
using System.Diagnostics;
using System.Security.Cryptography;

FromBase64Transform transform = new();
byte[] dest = new byte[2048];

byte[] buf1 = "AAA"u8.ToArray();
int written = transform.TransformBlock(buf1, 0, 3, dest, 0);

// Nothing should be written because it is a partial block the transform will hold on to.
Debug.Assert(written == 0);

// Transform a buffer that is exactly a power of two, meaning the ArrayPool (CryptoPool) will not be oversized.
byte[] buf2 = new byte[1024];
buf2.AsSpan().Fill((byte)'A');

written = transform.TransformBlock(buf2, 0, buf2.Length, dest, written);

Expected behavior

Should not throw an exception. Should transform as many blocks as possible to the destination.

Actual behavior

Unhandled exception. System.ArgumentException: Destination is too short. (Parameter 'destination')
   at System.Security.Cryptography.FromBase64Transform.AppendInputBuffers(ReadOnlySpan`1 inputBuffer, Span`1 transformBuffer)
   at System.Security.Cryptography.FromBase64Transform.TransformBlock(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount, Byte[] outputBuffer, Int32 outputOffset)
   at Program.<Main>$(String[] args) in /Users/vcsjones/Projects/scratch/Program.cs:line 18

Regression?

Unknown, but the code hasn't changed much since 2023.

Known Workarounds

Work around the rental boundaries.

Configuration

All configurations and OSes.

Other information

No response</issue_description>

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


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix FromBase64Transform renting from CryptoPool Fix FromBase64Transform incorrectly renting from CryptoPool Dec 31, 2025
Copilot AI requested a review from stephentoub December 31, 2025 18:14
@stephentoub stephentoub marked this pull request as ready for review December 31, 2025 19:57
Copilot AI review requested due to automatic review settings December 31, 2025 19:57
@dotnet-policy-service
Copy link
Contributor

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

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

This PR fixes a buffer sizing bug in FromBase64Transform where partial blocks followed by power-of-two sized inputs could cause an ArgumentException due to undersized buffer rental from CryptoPool.

Key Changes:

  • Fixed CryptoPool.Rent() calls to use bytesToTransform (retained + new bytes) instead of inputCount (new bytes only)
  • Added comprehensive tests to prevent regression of the specific bug scenario

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Base64Transforms.cs Fixed buffer sizing in TransformBlock and TransformFinalBlock by changing CryptoPool.Rent(inputCount) to CryptoPool.Rent(bytesToTransform) in both methods
src/libraries/System.Security.Cryptography/tests/Base64TransformsTests.cs Added two regression tests covering TransformBlock and TransformFinalBlock with partial blocks followed by power-of-two inputs

@stephentoub
Copy link
Member

@copilot please also fix #122807

…rsized output buffer

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 1, 2026

@copilot please also fix #122807

Fixed in e0622e7. Added handling for OperationStatus.DestinationTooSmall in ConvertFromBase64 to throw ArgumentOutOfRangeException("outputBuffer") instead of hitting the debug assert and incorrectly throwing FormatException.

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:

  • cacerts.digicert.com
    • 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.25613.101/build/../tools/net/xunit.console.dll System.Security.Cryptography.Tests.dll -xml testResults.xml -nologo -notrait category=OuterLoop -notrait category=failing (dns block)
  • http://www.microsoft.com:80/pki/certs/MicCodSigPCA_08-31-2010.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.25613.101/build/../tools/net/xunit.console.dll System.Security.Cryptography.Tests.dll -xml testResults.xml -nologo -notrait category=OuterLoop -notrait category=failing (http block)
  • 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.25613.101/build/../tools/net/xunit.console.dll System.Security.Cryptography.Tests.dll -xml testResults.xml -nologo -notrait category=OuterLoop -notrait category=failing (http block)

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

Copilot AI changed the title Fix FromBase64Transform incorrectly renting from CryptoPool Fix FromBase64Transform buffer sizing and output buffer handling Jan 1, 2026
Co-authored-by: vcsjones <361677+vcsjones@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 2, 2026

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

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:

  • cacerts.digicert.com
    • 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.25613.101/build/../tools/net/xunit.console.dll System.Security.Cryptography.Tests.dll -xml testResults.xml -nologo -notrait category=OuterLoop -notrait category=failing (dns block)
  • http://www.microsoft.com:80/pki/certs/MicCodSigPCA_08-31-2010.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.25613.101/build/../tools/net/xunit.console.dll System.Security.Cryptography.Tests.dll -xml testResults.xml -nologo -notrait category=OuterLoop -notrait category=failing (http block)
  • 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.25613.101/build/../tools/net/xunit.console.dll System.Security.Cryptography.Tests.dll -xml testResults.xml -nologo -notrait category=OuterLoop -notrait category=failing (http block)

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

Copilot AI requested a review from vcsjones January 2, 2026 16:08
@stephentoub stephentoub merged commit 6cf3563 into main Jan 3, 2026
87 checks passed
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 incorrectly rents from CryptoPool (ArrayPool)

3 participants