Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CryptoStream.Read(Byte[], Int32, Int32) read the wrong data #64144

Closed
ghost opened this issue Jan 22, 2022 · 9 comments
Closed

CryptoStream.Read(Byte[], Int32, Int32) read the wrong data #64144

ghost opened this issue Jan 22, 2022 · 9 comments
Labels
area-System.Security untriaged New issue has not been triaged by the area owner

Comments

@ghost
Copy link

ghost commented Jan 22, 2022

When I use class System.Security.Cryptography.CryptoStream to read my data. It came up with wrong data.

https://docs.microsoft.com/zh-cn/dotnet/api/system.security.cryptography.cryptostream.read

Test C# .net 6.0 with Visual Studio 2022

As it appears in complex projects. I simplified the process.

        public static int LoadData(CryptoStream stream)
        {
            var array = new byte[100];

            var count = stream.Read(array, 0, array.Length);
            return count;
        }

When this function is called, 100 bytes should be returned. But it only returned 99 bytes. Stream's length exceeds 10000.

I checked the C# source code and found the following code snippet.

private async ValueTask<int> ReadAsyncCore(Memory<byte> buffer, CancellationToken cancellationToken, bool useAsync)
        {
            while (true)
            {
                // If there are currently any bytes stored in the output buffer, hand back as many as we can.
                if (_outputBufferIndex != 0)
                {
                    int bytesToCopy = Math.Min(_outputBufferIndex, buffer.Length);
                    if (bytesToCopy != 0)
                    {
                        // Copy as many bytes as we can, then shift down the remaining bytes.
                        new ReadOnlySpan<byte>(_outputBuffer, 0, bytesToCopy).CopyTo(buffer.Span);
                        _outputBufferIndex -= bytesToCopy;
                        _outputBuffer.AsSpan(bytesToCopy).CopyTo(_outputBuffer);
                        CryptographicOperations.ZeroMemory(_outputBuffer.AsSpan(_outputBufferIndex, bytesToCopy));
                    }
                    return bytesToCopy;
                }

                // If we've already hit the end of the stream, there's nothing more to do.
                Debug.Assert(_outputBufferIndex == 0);
                if (_finalBlockTransformed)
                {
                    Debug.Assert(_inputBufferIndex == 0);
                    return 0;
                }

The key snippets are below.

 int bytesToCopy = Math.Min(_outputBufferIndex, buffer.Length);
 
 //other parts.
 return bytesToCopy;

Why does it return directly after comparing the minimum value of two numbers?

It doesn't count all the data based on the count parameter.

If it is because it reaches the end. But when I call the function again, it still can return values.
I can get the remaining 1 byte by calling it at the second time.

CryptoStream.Read(Byte[], Int32, Int32)
@dotnet-issue-labeler
Copy link

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@dotnet-issue-labeler dotnet-issue-labeler bot added the untriaged New issue has not been triaged by the area owner label Jan 22, 2022
@ghost
Copy link

ghost commented Jan 22, 2022

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

Issue Details

When I use class System.Security.Cryptography.CryptoStream to read my data. It came up with wrong data.

https://docs.microsoft.com/zh-cn/dotnet/api/system.security.cryptography.cryptostream.read

Test C# .net 6.0 with Visual Studio 2022

As it appears in complex projects. I simplified the process.

        public static int LoadData(CryptoStream stream)
        {
            var array = new byte[100];

            var count = stream.Read(array, 0, array.Length);
            return count;
        }

When this function is called, 100 bytes should be returned. But it only returned 99 bytes. Stream's length exceeds 10000.

I checked the C# source code and found the following code snippet.

private async ValueTask<int> ReadAsyncCore(Memory<byte> buffer, CancellationToken cancellationToken, bool useAsync)
        {
            while (true)
            {
                // If there are currently any bytes stored in the output buffer, hand back as many as we can.
                if (_outputBufferIndex != 0)
                {
                    int bytesToCopy = Math.Min(_outputBufferIndex, buffer.Length);
                    if (bytesToCopy != 0)
                    {
                        // Copy as many bytes as we can, then shift down the remaining bytes.
                        new ReadOnlySpan<byte>(_outputBuffer, 0, bytesToCopy).CopyTo(buffer.Span);
                        _outputBufferIndex -= bytesToCopy;
                        _outputBuffer.AsSpan(bytesToCopy).CopyTo(_outputBuffer);
                        CryptographicOperations.ZeroMemory(_outputBuffer.AsSpan(_outputBufferIndex, bytesToCopy));
                    }
                    return bytesToCopy;
                }

                // If we've already hit the end of the stream, there's nothing more to do.
                Debug.Assert(_outputBufferIndex == 0);
                if (_finalBlockTransformed)
                {
                    Debug.Assert(_inputBufferIndex == 0);
                    return 0;
                }

The key snippets are below.

 int bytesToCopy = Math.Min(_outputBufferIndex, buffer.Length);
 
 //other parts.
 return bytesToCopy;

Why does it return directly after comparing the minimum value of two numbers?

It doesn't count all the data based on the count parameter. If it is because it reaches the end. But when I call the function again, it still can return values.

CryptoStream.Read(Byte[], Int32, Int32)
Author: roland5572
Assignees: -
Labels:

area-System.Security, untriaged

Milestone: -

@vcsjones
Copy link
Member

This is a known breaking change that was introduced in .NET 6. See Partial and zero-byte reads in DeflateStream, GZipStream, and CryptoStream.

var count = stream.Read(array, 0, array.Length);

When this function is called, 100 bytes should be returned. But it only returned 99 bytes. Stream's length exceeds 10000.

That is not the contract for Stream.Read, which state:

An implementation is free to return fewer bytes than requested even if the end of the stream has not been reached.

In short, you need to call Read in a loop until you have read the number of desired (or all) bytes. The breaking change document includes examples of how to do so.

@ghost
Copy link
Author

ghost commented Jan 22, 2022

@vcsjones

Thank you.

I can see this code in your url:

int totalRead = 0;
while (totalRead < buffer.Length)
{
    int bytesRead = stream.Read(buffer.Slice(totalRead));
    if (bytesRead == 0) break;
    totalRead += bytesRead;
}

Do you think it can be provided as an new API function/method name by .NET? Because some users may not care much about the underlying logic, they just want to read enough data and be less prone to errors. Error-prone when duplicating these computational steps.

@vcsjones
Copy link
Member

Do you think it can be provided as an new API function/method name by .NET?

Possibly! If you feel that a new API would be useful for developers, feel free to write an API Proposal with your suggestion. From there, the API review process will be followed.

If you want help or prefer that I write the proposal, let me know.

@stephentoub
Copy link
Member

#16598

@ghost
Copy link
Author

ghost commented Jan 23, 2022

@vcsjones

Thank you. My english is not very good. I use Google Translate to convert languages. If you can fill a Proposal. It's the best for me.

@vcsjones
Copy link
Member

@stephentoub pointed out an existing proposal that looks exactly like the API you are describing (despite my attempt at finding an existing one). It hasn't had much traction, but we should avoid having duplicate proposals. I will at least see if there is interest in getting that one moving along again.

@stephentoub
Copy link
Member

#58216

@ghost ghost locked as resolved and limited conversation to collaborators Feb 23, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Security untriaged New issue has not been triaged by the area owner
Projects
None yet
Development

No branches or pull requests

2 participants