| | | 1 | | // Licensed to the .NET Foundation under one or more agreements. |
| | | 2 | | // The .NET Foundation licenses this file to you under the MIT license. |
| | | 3 | | |
| | | 4 | | using System.Buffers; |
| | | 5 | | using System.Buffers.Text; |
| | | 6 | | using System.Formats.Asn1; |
| | | 7 | | using System.Reflection; |
| | | 8 | | |
| | | 9 | | namespace DotnetFuzzing.Fuzzers |
| | | 10 | | { |
| | | 11 | | internal sealed class Base64UrlFuzzer : IFuzzer |
| | | 12 | | { |
| | 0 | 13 | | public string[] TargetAssemblies => []; |
| | | 14 | | |
| | 0 | 15 | | public string[] TargetCoreLibPrefixes => ["System.Buffers.Text"]; |
| | | 16 | | |
| | | 17 | | public void FuzzTarget(ReadOnlySpan<byte> bytes) |
| | 1435 | 18 | | { |
| | 1435 | 19 | | int encodedLength = Base64Url.GetEncodedLength(bytes.Length); |
| | 1435 | 20 | | int maxDecodedLength = Base64Url.GetMaxDecodedLength(encodedLength); |
| | | 21 | | |
| | 1435 | 22 | | using PooledBoundedMemory<byte> inputPoisoned = PooledBoundedMemory<byte>.Rent(bytes, PoisonPagePlacement.Af |
| | 1435 | 23 | | using PooledBoundedMemory<char> destPoisoned = PooledBoundedMemory<char>.Rent(encodedLength, PoisonPagePlace |
| | 1435 | 24 | | using PooledBoundedMemory<byte> decoderDestPoisoned = PooledBoundedMemory<byte>.Rent(maxDecodedLength, Poiso |
| | | 25 | | |
| | 1435 | 26 | | Span<byte> input = inputPoisoned.Span; |
| | 1435 | 27 | | Span<char> encoderDest = destPoisoned.Span; |
| | 1435 | 28 | | Span<byte> decoderDest = decoderDestPoisoned.Span; |
| | | 29 | | |
| | 1435 | 30 | | { // IsFinalBlock = true |
| | 1435 | 31 | | OperationStatus status = Base64Url.EncodeToChars(input, encoderDest, out int bytesConsumed, out int byte |
| | | 32 | | |
| | 1435 | 33 | | Assert.Equal(OperationStatus.Done, status); |
| | 1435 | 34 | | Assert.Equal(bytes.Length, bytesConsumed); |
| | 1435 | 35 | | Assert.Equal(true, encodedLength == bytesEncoded); |
| | | 36 | | |
| | 1435 | 37 | | string encodedString = Base64Url.EncodeToString(input); |
| | 1435 | 38 | | Assert.Equal(encodedString, new string(encoderDest)); |
| | | 39 | | |
| | 1435 | 40 | | status = Base64Url.DecodeFromChars(encoderDest, decoderDest, out int bytesRead, out int bytesDecoded); |
| | | 41 | | |
| | 1435 | 42 | | Assert.Equal(OperationStatus.Done, status); |
| | 1435 | 43 | | Assert.Equal(bytes.Length, bytesDecoded); |
| | 1435 | 44 | | Assert.Equal(bytesEncoded, bytesRead); |
| | 1435 | 45 | | Assert.SequenceEqual(bytes, decoderDest.Slice(0, bytesDecoded)); |
| | 1435 | 46 | | } |
| | | 47 | | |
| | 1435 | 48 | | { // IsFinalBlock = false |
| | 1435 | 49 | | encoderDest.Clear(); |
| | 1435 | 50 | | decoderDest.Clear(); |
| | 1435 | 51 | | OperationStatus status = Base64Url.EncodeToChars(input, encoderDest, out int bytesConsumed, out int byte |
| | 1435 | 52 | | Span<char> decodeInput = encoderDest.Slice(0, bytesEncoded); |
| | | 53 | | |
| | 1435 | 54 | | if (bytes.Length % 3 == 0) |
| | 549 | 55 | | { |
| | 549 | 56 | | Assert.Equal(OperationStatus.Done, status); |
| | 549 | 57 | | Assert.Equal(bytes.Length, bytesConsumed); |
| | 549 | 58 | | Assert.Equal(true, encodedLength == bytesEncoded); |
| | | 59 | | |
| | 549 | 60 | | status = Base64Url.DecodeFromChars(decodeInput, decoderDest, out int bytesRead, out int bytesDecoded |
| | | 61 | | |
| | 549 | 62 | | Assert.Equal(OperationStatus.Done, status); |
| | 549 | 63 | | Assert.Equal(bytes.Length, bytesDecoded); |
| | 549 | 64 | | Assert.Equal(bytesEncoded, bytesRead); |
| | 549 | 65 | | Assert.SequenceEqual(bytes, decoderDest.Slice(0, bytesDecoded)); |
| | 549 | 66 | | } |
| | | 67 | | else |
| | 886 | 68 | | { |
| | 886 | 69 | | Assert.Equal(OperationStatus.NeedMoreData, status); |
| | 886 | 70 | | Assert.Equal(true, input.Length / 3 * 4 == bytesEncoded); |
| | | 71 | | |
| | 886 | 72 | | status = Base64Url.DecodeFromChars(decodeInput, decoderDest, out int bytesRead, out int bytesDecoded |
| | | 73 | | |
| | 886 | 74 | | if (decodeInput.Length % 4 == 0) |
| | 886 | 75 | | { |
| | 886 | 76 | | Assert.Equal(OperationStatus.Done, status); |
| | 886 | 77 | | Assert.Equal(bytesConsumed, bytesDecoded); |
| | 886 | 78 | | Assert.Equal(bytesEncoded, bytesRead); |
| | 886 | 79 | | } |
| | | 80 | | else |
| | 0 | 81 | | { |
| | 0 | 82 | | Assert.Equal(OperationStatus.NeedMoreData, status); |
| | 0 | 83 | | } |
| | | 84 | | |
| | 886 | 85 | | Assert.SequenceEqual(bytes.Slice(0, bytesDecoded), decoderDest.Slice(0, bytesDecoded)); |
| | 886 | 86 | | } |
| | 1435 | 87 | | } |
| | | 88 | | |
| | 1435 | 89 | | { // Encode / decode in place |
| | 1435 | 90 | | using PooledBoundedMemory<byte> destPoisoned2 = PooledBoundedMemory<byte>.Rent(encodedLength, PoisonPage |
| | 1435 | 91 | | Span<byte> tryEncode = destPoisoned2.Span; |
| | 1435 | 92 | | input.CopyTo(tryEncode); |
| | | 93 | | |
| | 1435 | 94 | | Assert.Equal(true, Base64Url.TryEncodeToUtf8InPlace(tryEncode, input.Length, out int bytesEncoded)); |
| | 1435 | 95 | | Assert.Equal(encodedLength, bytesEncoded); |
| | | 96 | | |
| | 1435 | 97 | | int bytesDecoded = Base64Url.DecodeFromUtf8InPlace(tryEncode); |
| | | 98 | | |
| | 1435 | 99 | | Assert.Equal(bytes.Length, bytesDecoded); |
| | 1435 | 100 | | Assert.SequenceEqual(bytes, tryEncode.Slice(0, bytesDecoded)); |
| | 1435 | 101 | | } |
| | | 102 | | |
| | 1435 | 103 | | { // Decode the random input directly, Assert IsValid result matches with decoded result |
| | 1435 | 104 | | decoderDest.Clear(); |
| | 1435 | 105 | | if (Base64Url.IsValid(input, out int decodedLength)) |
| | 678 | 106 | | { |
| | 678 | 107 | | maxDecodedLength = Base64Url.GetMaxDecodedLength(input.Length); |
| | 678 | 108 | | OperationStatus status = Base64Url.DecodeFromUtf8(input, decoderDest, out int bytesRead, out int byt |
| | 678 | 109 | | Assert.Equal(OperationStatus.Done, status); |
| | 678 | 110 | | Assert.Equal(input.Length, bytesRead); |
| | 678 | 111 | | Assert.Equal(decodedLength, bytesDecoded); |
| | 678 | 112 | | Assert.Equal(true, maxDecodedLength >= bytesDecoded); |
| | | 113 | | |
| | 678 | 114 | | Span<byte> tryDecode = new byte[maxDecodedLength]; |
| | 678 | 115 | | Assert.Equal(true, Base64Url.TryDecodeFromUtf8(input, tryDecode, out bytesDecoded)); |
| | 678 | 116 | | Assert.Equal(decodedLength, bytesDecoded); |
| | 678 | 117 | | Assert.SequenceEqual<byte>(tryDecode.Slice(0, bytesDecoded), decoderDest.Slice(0, bytesDecoded)); |
| | | 118 | | |
| | 678 | 119 | | int decoded = Base64Url.DecodeFromUtf8InPlace(input); |
| | 678 | 120 | | Assert.Equal(OperationStatus.Done, status); |
| | 678 | 121 | | Assert.Equal(bytesDecoded, decoded); |
| | 678 | 122 | | Assert.SequenceEqual<byte>(input.Slice(0, decoded), decoderDest.Slice(0, bytesDecoded)); |
| | 678 | 123 | | } |
| | | 124 | | else |
| | 757 | 125 | | { |
| | 757 | 126 | | Assert.Equal(OperationStatus.InvalidData, Base64Url.DecodeFromUtf8(input, decoderDest, out int _, ou |
| | | 127 | | try |
| | 757 | 128 | | { |
| | 757 | 129 | | Assert.Equal(0, Base64Url.DecodeFromUtf8InPlace(input)); |
| | 0 | 130 | | } |
| | 2271 | 131 | | catch (FormatException) { /* DecodeFromUtf8InPlace would throw FormatException for InvalidData*/ } |
| | 757 | 132 | | } |
| | 1435 | 133 | | } |
| | 2870 | 134 | | } |
| | | 135 | | } |
| | | 136 | | } |