1
1
// Licensed to the .NET Foundation under one or more agreements.
2
2
// The .NET Foundation licenses this file to you under the MIT license.
3
3
4
-
5
4
using System . Buffers ;
6
5
using System . Buffers . Text ;
7
6
8
7
namespace DotnetFuzzing . Fuzzers
9
8
{
10
9
internal sealed class Base64Fuzzer : IFuzzer
11
10
{
11
+ private const int Base64LineBreakPosition = 76 ; // Needs to be in sync with Convert.Base64LineBreakPosition
12
+
12
13
public string [ ] TargetAssemblies => [ ] ;
13
14
14
- public string [ ] TargetCoreLibPrefixes => [ "System.Buffers.Text" ] ;
15
+ public string [ ] TargetCoreLibPrefixes => [ "System.Buffers.Text.Base64" , "System.Convert "] ;
15
16
16
17
public void FuzzTarget ( ReadOnlySpan < byte > bytes )
17
18
{
18
- using PooledBoundedMemory < byte > inputPoisoned = PooledBoundedMemory < byte > . Rent ( bytes , PoisonPagePlacement . After ) ;
19
- Span < byte > input = inputPoisoned . Span ;
20
- int maxEncodedLength = Base64 . GetMaxEncodedToUtf8Length ( bytes . Length ) ;
21
- using PooledBoundedMemory < byte > destPoisoned = PooledBoundedMemory < byte > . Rent ( maxEncodedLength , PoisonPagePlacement . After ) ;
19
+ using PooledBoundedMemory < byte > inputPoisonBefore = PooledBoundedMemory < byte > . Rent ( bytes , PoisonPagePlacement . Before ) ;
20
+ using PooledBoundedMemory < byte > inputPoisonAfter = PooledBoundedMemory < byte > . Rent ( bytes , PoisonPagePlacement . After ) ;
21
+
22
+ TestCases ( inputPoisonBefore . Span , PoisonPagePlacement . Before ) ;
23
+ TestCases ( inputPoisonAfter . Span , PoisonPagePlacement . After ) ;
24
+ }
25
+
26
+ private void TestCases ( Span < byte > input , PoisonPagePlacement poison )
27
+ {
28
+ TestBase64 ( input , poison ) ;
29
+ TestToStringToCharArray ( input , Base64FormattingOptions . None ) ;
30
+ TestToStringToCharArray ( input , Base64FormattingOptions . InsertLineBreaks ) ;
31
+ }
32
+
33
+ private void TestBase64 ( Span < byte > input , PoisonPagePlacement poison )
34
+ {
35
+ int maxEncodedLength = Base64 . GetMaxEncodedToUtf8Length ( input . Length ) ;
36
+ using PooledBoundedMemory < byte > destPoisoned = PooledBoundedMemory < byte > . Rent ( maxEncodedLength , poison ) ;
22
37
Span < byte > encoderDest = destPoisoned . Span ;
23
- using PooledBoundedMemory < byte > decoderDestPoisoned = PooledBoundedMemory < byte > . Rent ( Base64 . GetMaxDecodedFromUtf8Length ( maxEncodedLength ) , PoisonPagePlacement . After ) ;
38
+ using PooledBoundedMemory < byte > decoderDestPoisoned = PooledBoundedMemory < byte > . Rent ( Base64 . GetMaxDecodedFromUtf8Length ( maxEncodedLength ) , poison ) ;
24
39
Span < byte > decoderDest = decoderDestPoisoned . Span ;
25
40
{ // IsFinalBlock = true
26
41
OperationStatus status = Base64 . EncodeToUtf8 ( input , encoderDest , out int bytesConsumed , out int bytesEncoded ) ;
27
-
28
42
Assert . Equal ( OperationStatus . Done , status ) ;
29
- Assert . Equal ( bytes . Length , bytesConsumed ) ;
43
+ Assert . Equal ( input . Length , bytesConsumed ) ;
30
44
Assert . Equal ( true , maxEncodedLength >= bytesEncoded && maxEncodedLength - 2 <= bytesEncoded ) ;
31
45
32
46
status = Base64 . DecodeFromUtf8 ( encoderDest . Slice ( 0 , bytesEncoded ) , decoderDest , out int bytesRead , out int bytesDecoded ) ;
33
47
34
48
Assert . Equal ( OperationStatus . Done , status ) ;
35
- Assert . Equal ( bytes . Length , bytesDecoded ) ;
49
+ Assert . Equal ( input . Length , bytesDecoded ) ;
36
50
Assert . Equal ( bytesEncoded , bytesRead ) ;
37
- Assert . SequenceEqual ( bytes , decoderDest . Slice ( 0 , bytesDecoded ) ) ;
51
+ Assert . SequenceEqual ( input , decoderDest . Slice ( 0 , bytesDecoded ) ) ;
38
52
}
39
53
40
54
{ // IsFinalBlock = false
@@ -43,18 +57,18 @@ public void FuzzTarget(ReadOnlySpan<byte> bytes)
43
57
OperationStatus status = Base64 . EncodeToUtf8 ( input , encoderDest , out int bytesConsumed , out int bytesEncoded , isFinalBlock : false ) ;
44
58
Span < byte > decodeInput = encoderDest . Slice ( 0 , bytesEncoded ) ;
45
59
46
- if ( bytes . Length % 3 == 0 )
60
+ if ( input . Length % 3 == 0 )
47
61
{
48
62
Assert . Equal ( OperationStatus . Done , status ) ;
49
- Assert . Equal ( bytes . Length , bytesConsumed ) ;
63
+ Assert . Equal ( input . Length , bytesConsumed ) ;
50
64
Assert . Equal ( true , maxEncodedLength == bytesEncoded ) ;
51
65
52
66
status = Base64 . DecodeFromUtf8 ( decodeInput , decoderDest , out int bytesRead , out int bytesDecoded , isFinalBlock : false ) ;
53
67
54
68
Assert . Equal ( OperationStatus . Done , status ) ;
55
- Assert . Equal ( bytes . Length , bytesDecoded ) ;
69
+ Assert . Equal ( input . Length , bytesDecoded ) ;
56
70
Assert . Equal ( bytesEncoded , bytesRead ) ;
57
- Assert . SequenceEqual ( bytes , decoderDest . Slice ( 0 , bytesDecoded ) ) ;
71
+ Assert . SequenceEqual ( input , decoderDest . Slice ( 0 , bytesDecoded ) ) ;
58
72
}
59
73
else
60
74
{
@@ -74,7 +88,7 @@ public void FuzzTarget(ReadOnlySpan<byte> bytes)
74
88
Assert . Equal ( OperationStatus . NeedMoreData , status ) ;
75
89
}
76
90
77
- Assert . SequenceEqual ( bytes . Slice ( 0 , bytesDecoded ) , decoderDest . Slice ( 0 , bytesDecoded ) ) ;
91
+ Assert . SequenceEqual ( input . Slice ( 0 , bytesDecoded ) , decoderDest . Slice ( 0 , bytesDecoded ) ) ;
78
92
}
79
93
}
80
94
@@ -89,8 +103,8 @@ public void FuzzTarget(ReadOnlySpan<byte> bytes)
89
103
status = Base64 . DecodeFromUtf8InPlace ( encoderDest . Slice ( 0 , bytesEncoded ) , out int bytesDecoded ) ;
90
104
91
105
Assert . Equal ( OperationStatus . Done , status ) ;
92
- Assert . Equal ( bytes . Length , bytesDecoded ) ;
93
- Assert . SequenceEqual ( bytes , encoderDest . Slice ( 0 , bytesDecoded ) ) ;
106
+ Assert . Equal ( input . Length , bytesDecoded ) ;
107
+ Assert . SequenceEqual ( input , encoderDest . Slice ( 0 , bytesDecoded ) ) ;
94
108
}
95
109
96
110
{ // Decode the random input directly, Assert IsValid result matches with decoded result
@@ -116,5 +130,42 @@ public void FuzzTarget(ReadOnlySpan<byte> bytes)
116
130
}
117
131
}
118
132
}
133
+
134
+ private static void TestToStringToCharArray ( Span < byte > input , Base64FormattingOptions options )
135
+ {
136
+ int encodedLength = ToBase64_CalculateOutputLength ( input . Length , options == Base64FormattingOptions . InsertLineBreaks ) ;
137
+ char [ ] dest = new char [ encodedLength ] ;
138
+
139
+ string toStringResult = Convert . ToBase64String ( input , options ) ;
140
+ byte [ ] decoded = Convert . FromBase64String ( toStringResult ) ;
141
+
142
+ Assert . SequenceEqual ( input , decoded ) ;
143
+
144
+ int written = Convert . ToBase64CharArray ( input . ToArray ( ) , 0 , input . Length , dest , 0 , options ) ;
145
+ decoded = Convert . FromBase64CharArray ( dest , 0 , written ) ;
146
+
147
+ Assert . SequenceEqual ( input , decoded ) ;
148
+ Assert . SequenceEqual ( toStringResult . AsSpan ( ) , dest . AsSpan ( 0 , written ) ) ;
149
+ }
150
+
151
+ private static int ToBase64_CalculateOutputLength ( int inputLength , bool insertLineBreaks )
152
+ {
153
+ uint outlen = ( ( uint ) inputLength + 2 ) / 3 * 4 ;
154
+
155
+ if ( outlen == 0 )
156
+ return 0 ;
157
+
158
+ if ( insertLineBreaks )
159
+ {
160
+ ( uint newLines , uint remainder ) = Math . DivRem ( outlen , Base64LineBreakPosition ) ;
161
+ if ( remainder == 0 )
162
+ {
163
+ -- newLines ;
164
+ }
165
+ outlen += newLines * 2 ; // 2 line break chars added: "\r\n"
166
+ }
167
+
168
+ return ( int ) outlen ;
169
+ }
119
170
}
120
171
}
0 commit comments