[TLS]: Fix #23792.
authorMartin Baulig <martin.baulig@xamarin.com>
Wed, 20 May 2015 01:00:34 +0000 (03:00 +0200)
committerMartin Baulig <martin.baulig@xamarin.com>
Wed, 20 May 2015 01:12:26 +0000 (03:12 +0200)
What happens here is the following:

When using chunked encoding, the ChunkStream sometimes mixes async and sync requests
on the underlying stream.  The async calls work great - just the sync ones create
problems.

In this particular bug, the server returns two ApplicationData records - one big one
and then a smaller one.  The chunk parser requests a sync read of 1024 bytes which
then result in a series of sync reads on the underlying network stream.  The first
two or three network reads don't return enough data to fill the huge ApplicationData
record - the last one then returns so much data that it fills both the remainder of
the first record and the entire second one.

If the caller issued a second sync read, then the implementation would catch this -
but the async code path does not.

mcs/class/Mono.Security/Mono.Security.Protocol.Tls/SslStreamBase.cs

index e257296cc23f828360bde833433c92bc8a13a8ae..41271cdb3267a0cca279e766a3167b6143da7c96 100644 (file)
@@ -601,6 +601,9 @@ namespace Mono.Security.Protocol.Tls
                                {
                                        asyncResult.SetComplete(preReadSize);
                                }
+                               else if (recordStream.Position < recordStream.Length) {
+                                       InternalReadCallback_inner (asyncResult, recbuf, new object[] { recbuf, asyncResult }, false, 0);
+                               }
                                else if (!this.context.ReceivedConnectionEnd)
                                {
                                        // this will read data from the network until we have (at least) one
@@ -650,6 +653,24 @@ namespace Mono.Security.Protocol.Tls
                                        return;
                                }
 
+                               InternalReadCallback_inner(internalResult, recbuf, state, true, n);
+                       }
+                       catch (Exception ex)
+                       {
+                               internalResult.SetComplete(ex);
+                       }
+
+               }
+
+               // read encrypted data until we have enough to decrypt (at least) one
+               // record and return are the records (may be more than one) we have
+               private void InternalReadCallback_inner(InternalAsyncResult internalResult, byte[] recbuf, object[] state, bool didRead, int n)
+               {
+                       if (this.disposed)
+                               return;
+
+                       try
+                       {
                                bool dataToReturn = false;
                                long pos = recordStream.Position;
 
@@ -713,7 +734,7 @@ namespace Mono.Security.Protocol.Tls
                                                pos = 0;
                                }
 
-                               if (!dataToReturn && (n > 0))
+                               if (!dataToReturn && (!didRead || (n > 0)))
                                {
                                        if (context.ReceivedConnectionEnd) {
                                                internalResult.SetComplete (0);
@@ -744,7 +765,6 @@ namespace Mono.Security.Protocol.Tls
                        {
                                internalResult.SetComplete(ex);
                        }
-
                }
 
                private void InternalBeginWrite(InternalAsyncResult asyncResult)
@@ -1022,6 +1042,7 @@ namespace Mono.Security.Protocol.Tls
 
                                                        if (remainder > 0) {
                                                                recordStream.Write (outofrecord, 0, outofrecord.Length);
+                                                               recordStream.Position = 0;
                                                        }
 
                                                        if (dataToReturn) {