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

Fix non-thread-safe use of Random in MultipartFormDataBinaryContent #36

Merged
merged 3 commits into from
Jun 14, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 36 additions & 20 deletions src/Utility/MultipartFormDataBinaryContent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ internal class MultipartFormDataBinaryContent : BinaryContent
{
private readonly MultipartFormDataContent _multipartContent;

private static Random _random = new();
private static readonly char[] _boundaryValues = "0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".ToCharArray();
private const int BoundaryLength = 70;
private const string BoundaryValues = "0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz";

public MultipartFormDataBinaryContent()
{
Expand Down Expand Up @@ -120,27 +120,44 @@ public static void AddContentTypeHeader(HttpContent content, string contentType)
content.Headers.ContentType = header;
}

#if NET6_0_OR_GREATER
private static string CreateBoundary() =>
string.Create(BoundaryLength, 0, (chars, _) =>
{
Span<byte> random = stackalloc byte[BoundaryLength];
Random.Shared.NextBytes(random);

for (int i = 0; i < chars.Length; i++)
{
chars[i] = BoundaryValues[random[i] % BoundaryValues.Length];
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
}
});
#else
private static readonly Random _random = new();

private static string CreateBoundary()
{
Span<char> chars = new char[70];
Span<char> chars = stackalloc char[BoundaryLength];

byte[] random = new byte[70];
_random.NextBytes(random);

// The following will sample evenly from the possible values.
// This is important to ensuring that the odds of creating a boundary
// that occurs in any content part are astronomically small.
int mask = 255 >> 2;
byte[] random = new byte[BoundaryLength];
trrwilson marked this conversation as resolved.
Show resolved Hide resolved
lock (_random)
{
_random.NextBytes(random);
}

Debug.Assert(_boundaryValues.Length - 1 == mask);
// Instead of `% BoundaryValues.Length` as is used above, use a mask to achieve the same result.
// `% BoundaryValues.Length` is optimized to the equivalent on .NET Core but not on .NET Framework.
const int Mask = 255 >> 2;
Debug.Assert(BoundaryValues.Length - 1 == Mask);

for (int i = 0; i < 70; i++)
for (int i = 0; i < chars.Length; i++)
{
chars[i] = _boundaryValues[random[i] & mask];
chars[i] = BoundaryValues[random[i] & Mask];
}

return chars.ToString();
}
#endif

public override bool TryComputeLength(out long length)
{
Expand All @@ -158,22 +175,21 @@ public override bool TryComputeLength(out long length)

public override void WriteTo(Stream stream, CancellationToken cancellationToken = default)
{
// TODO: polyfill sync-over-async for netstandard2.0 for Azure clients.
// Tracked by https://github.com/Azure/azure-sdk-for-net/issues/42674

#if NET6_0_OR_GREATER
#if NET5_0_OR_GREATER
_multipartContent.CopyTo(stream, default, cancellationToken);
#else
_multipartContent.CopyToAsync(stream).GetAwaiter().GetResult();
// TODO: polyfill sync-over-async for netstandard2.0 for Azure clients.
// Tracked by https://github.com/Azure/azure-sdk-for-net/issues/42674
_multipartContent.CopyToAsync(stream).GetAwaiter().GetResult();
#endif
}

public override async Task WriteToAsync(Stream stream, CancellationToken cancellationToken = default)
{
#if NET6_0_OR_GREATER
#if NET5_0_OR_GREATER
await _multipartContent.CopyToAsync(stream, cancellationToken).ConfigureAwait(false);
#else
await _multipartContent.CopyToAsync(stream).ConfigureAwait(false);
await _multipartContent.CopyToAsync(stream).ConfigureAwait(false);
#endif
}

Expand Down
Loading