7
7
using System . Security . Authentication ;
8
8
using System . Security . Cryptography ;
9
9
using System . Security . Cryptography . X509Certificates ;
10
+ using System . Threading ;
10
11
11
12
using PAL_KeyAlgorithm = Interop . AndroidCrypto . PAL_KeyAlgorithm ;
12
13
using PAL_SSLStreamStatus = Interop . AndroidCrypto . PAL_SSLStreamStatus ;
@@ -29,13 +30,17 @@ internal sealed class SafeDeleteSslContext : SafeDeleteContext
29
30
30
31
private readonly SafeSslHandle _sslContext ;
31
32
33
+ private readonly Lock _lock = new Lock ( ) ;
34
+
32
35
private ArrayBuffer _inputBuffer = new ArrayBuffer ( InitialBufferSize ) ;
33
36
private ArrayBuffer _outputBuffer = new ArrayBuffer ( InitialBufferSize ) ;
34
37
35
38
public SslStream . JavaProxy SslStreamProxy { get ; }
36
39
37
40
public SafeSslHandle SslContext => _sslContext ;
38
41
42
+ private volatile bool _disposed ;
43
+
39
44
public SafeDeleteSslContext ( SslAuthenticationOptions authOptions )
40
45
: base ( IntPtr . Zero )
41
46
{
@@ -59,13 +64,21 @@ public SafeDeleteSslContext(SslAuthenticationOptions authOptions)
59
64
60
65
protected override void Dispose ( bool disposing )
61
66
{
62
- if ( disposing )
67
+ lock ( _lock )
63
68
{
64
- if ( _sslContext is SafeSslHandle sslContext )
69
+ if ( ! _disposed )
65
70
{
66
- _inputBuffer . Dispose ( ) ;
67
- _outputBuffer . Dispose ( ) ;
68
- sslContext . Dispose ( ) ;
71
+ _disposed = true ;
72
+
73
+ // First dispose the SSL context to trigger native cleanup
74
+ _sslContext . Dispose ( ) ;
75
+
76
+ if ( disposing )
77
+ {
78
+ // Then dispose the buffers
79
+ _inputBuffer . Dispose ( ) ;
80
+ _outputBuffer . Dispose ( ) ;
81
+ }
69
82
}
70
83
}
71
84
@@ -75,61 +88,105 @@ protected override void Dispose(bool disposing)
75
88
[ UnmanagedCallersOnly ]
76
89
private static unsafe void WriteToConnection ( IntPtr connection , byte * data , int dataLength )
77
90
{
78
- SafeDeleteSslContext ? context = ( SafeDeleteSslContext ? ) GCHandle . FromIntPtr ( connection ) . Target ;
79
- Debug . Assert ( context != null ) ;
91
+ WeakGCHandle < SafeDeleteSslContext > h = WeakGCHandle < SafeDeleteSslContext > . FromIntPtr ( connection ) ;
92
+ if ( ! h . TryGetTarget ( out SafeDeleteSslContext ? context ) )
93
+ {
94
+ Debug . Write ( "WriteToConnection: failed to get target context" ) ;
95
+ return ;
96
+ }
97
+
98
+ lock ( context . _lock )
99
+ {
100
+ if ( context . _disposed )
101
+ {
102
+ Debug . Write ( "WriteToConnection: context is disposed" ) ;
103
+ return ;
104
+ }
80
105
81
- var inputBuffer = new ReadOnlySpan < byte > ( data , dataLength ) ;
106
+ var inputBuffer = new ReadOnlySpan < byte > ( data , dataLength ) ;
82
107
83
- context . _outputBuffer . EnsureAvailableSpace ( dataLength ) ;
84
- inputBuffer . CopyTo ( context . _outputBuffer . AvailableSpan ) ;
85
- context . _outputBuffer . Commit ( dataLength ) ;
108
+ context . _outputBuffer . EnsureAvailableSpace ( dataLength ) ;
109
+ inputBuffer . CopyTo ( context . _outputBuffer . AvailableSpan ) ;
110
+ context . _outputBuffer . Commit ( dataLength ) ;
111
+ }
86
112
}
87
113
88
114
[ UnmanagedCallersOnly ]
89
115
private static unsafe PAL_SSLStreamStatus ReadFromConnection ( IntPtr connection , byte * data , int * dataLength )
90
116
{
91
- SafeDeleteSslContext ? context = ( SafeDeleteSslContext ? ) GCHandle . FromIntPtr ( connection ) . Target ;
92
- Debug . Assert ( context != null ) ;
93
-
94
- int toRead = * dataLength ;
95
- if ( toRead == 0 )
96
- return PAL_SSLStreamStatus . OK ;
97
-
98
- if ( context . _inputBuffer . ActiveLength == 0 )
117
+ WeakGCHandle < SafeDeleteSslContext > h = WeakGCHandle < SafeDeleteSslContext > . FromIntPtr ( connection ) ;
118
+ if ( ! h . TryGetTarget ( out SafeDeleteSslContext ? context ) )
99
119
{
120
+ Debug . Write ( "ReadFromConnection: failed to get target context" ) ;
100
121
* dataLength = 0 ;
101
- return PAL_SSLStreamStatus . NeedData ;
122
+ return PAL_SSLStreamStatus . Error ;
102
123
}
103
124
104
- toRead = Math . Min ( toRead , context . _inputBuffer . ActiveLength ) ;
125
+ lock ( context . _lock )
126
+ {
127
+ if ( context . _disposed )
128
+ {
129
+ Debug . Write ( "ReadFromConnection: context is disposed" ) ;
130
+ * dataLength = 0 ;
131
+ return PAL_SSLStreamStatus . Error ;
132
+ }
133
+
134
+ int toRead = * dataLength ;
135
+ if ( toRead == 0 )
136
+ return PAL_SSLStreamStatus . OK ;
137
+
138
+ if ( context . _inputBuffer . ActiveLength == 0 )
139
+ {
140
+ * dataLength = 0 ;
141
+ return PAL_SSLStreamStatus . NeedData ;
142
+ }
143
+
144
+ toRead = Math . Min ( toRead , context . _inputBuffer . ActiveLength ) ;
105
145
106
- context . _inputBuffer . ActiveSpan . Slice ( 0 , toRead ) . CopyTo ( new Span < byte > ( data , toRead ) ) ;
107
- context . _inputBuffer . Discard ( toRead ) ;
146
+ context . _inputBuffer . ActiveSpan . Slice ( 0 , toRead ) . CopyTo ( new Span < byte > ( data , toRead ) ) ;
147
+ context . _inputBuffer . Discard ( toRead ) ;
148
+
149
+ * dataLength = toRead ;
150
+ return PAL_SSLStreamStatus . OK ;
151
+ }
152
+ }
108
153
109
- * dataLength = toRead ;
110
- return PAL_SSLStreamStatus . OK ;
154
+ [ UnmanagedCallersOnly ]
155
+ private static void CleanupManagedContext ( IntPtr managedContextHandle )
156
+ {
157
+ if ( managedContextHandle != IntPtr . Zero )
158
+ {
159
+ WeakGCHandle < SafeDeleteSslContext > handle = WeakGCHandle < SafeDeleteSslContext > . FromIntPtr ( managedContextHandle ) ;
160
+ handle . Dispose ( ) ;
161
+ }
111
162
}
112
163
113
164
internal void Write ( ReadOnlySpan < byte > buf )
114
165
{
115
- _inputBuffer . EnsureAvailableSpace ( buf . Length ) ;
116
- buf . CopyTo ( _inputBuffer . AvailableSpan ) ;
117
- _inputBuffer . Commit ( buf . Length ) ;
166
+ lock ( _lock )
167
+ {
168
+ _inputBuffer . EnsureAvailableSpace ( buf . Length ) ;
169
+ buf . CopyTo ( _inputBuffer . AvailableSpan ) ;
170
+ _inputBuffer . Commit ( buf . Length ) ;
171
+ }
118
172
}
119
173
120
174
internal int BytesReadyForConnection => _outputBuffer . ActiveLength ;
121
175
122
176
internal void ReadPendingWrites ( ref ProtocolToken token )
123
177
{
124
- if ( _outputBuffer . ActiveLength == 0 )
178
+ lock ( _lock )
125
179
{
126
- token . Size = 0 ;
127
- token . Payload = null ;
128
- return ;
129
- }
180
+ if ( _outputBuffer . ActiveLength == 0 )
181
+ {
182
+ token . Size = 0 ;
183
+ token . Payload = null ;
184
+ return ;
185
+ }
130
186
131
- token . SetPayload ( _outputBuffer . ActiveSpan ) ;
132
- _outputBuffer . Discard ( _outputBuffer . ActiveLength ) ;
187
+ token . SetPayload ( _outputBuffer . ActiveSpan ) ;
188
+ _outputBuffer . Discard ( _outputBuffer . ActiveLength ) ;
189
+ }
133
190
}
134
191
135
192
internal int ReadPendingWrites ( byte [ ] buf , int offset , int count )
@@ -139,12 +196,15 @@ internal int ReadPendingWrites(byte[] buf, int offset, int count)
139
196
Debug . Assert ( count >= 0 ) ;
140
197
Debug . Assert ( count <= buf . Length - offset ) ;
141
198
142
- int limit = Math . Min ( count , _outputBuffer . ActiveLength ) ;
199
+ lock ( _lock )
200
+ {
201
+ int limit = Math . Min ( count , _outputBuffer . ActiveLength ) ;
143
202
144
- _outputBuffer . ActiveSpan . Slice ( 0 , limit ) . CopyTo ( new Span < byte > ( buf , offset , limit ) ) ;
145
- _outputBuffer . Discard ( limit ) ;
203
+ _outputBuffer . ActiveSpan . Slice ( 0 , limit ) . CopyTo ( new Span < byte > ( buf , offset , limit ) ) ;
204
+ _outputBuffer . Discard ( limit ) ;
146
205
147
- return limit ;
206
+ return limit ;
207
+ }
148
208
}
149
209
150
210
private static SafeSslHandle CreateSslContext ( SslStream . JavaProxy sslStreamProxy , SslAuthenticationOptions authOptions )
@@ -225,11 +285,11 @@ private unsafe void InitializeSslContext(
225
285
throw new NotImplementedException ( nameof ( SafeDeleteSslContext ) ) ;
226
286
}
227
287
228
- // Make sure the class instance is associated to the session and is provided
229
- // in the Read/Write callback connection parameter
288
+ // Make sure the class instance is associated to the session and is provided in the Read/Write callback connection parameter
289
+ // Additionally, all calls should be synchronous so there's no risk of the managed object being collected while native code is executing.
230
290
IntPtr managedContextHandle = GCHandle . ToIntPtr ( GCHandle . Alloc ( this , GCHandleType . Weak ) ) ;
231
291
string ? peerHost = ! isServer && ! string . IsNullOrEmpty ( authOptions . TargetHost ) ? authOptions . TargetHost : null ;
232
- Interop . AndroidCrypto . SSLStreamInitialize ( handle , isServer , managedContextHandle , & ReadFromConnection , & WriteToConnection , InitialBufferSize , peerHost ) ;
292
+ Interop . AndroidCrypto . SSLStreamInitialize ( handle , isServer , managedContextHandle , & ReadFromConnection , & WriteToConnection , & CleanupManagedContext , InitialBufferSize , peerHost ) ;
233
293
234
294
if ( authOptions . EnabledSslProtocols != SslProtocols . None )
235
295
{
0 commit comments