This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/master by this push:
     new b9b3fa7  Fix bz 65137 Don't corrupt response on early termination
b9b3fa7 is described below

commit b9b3fa78a00e76b6344ababb1fd28cf20561cb82
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Fri Feb 19 15:33:56 2021 +0000

    Fix bz 65137 Don't corrupt response on early termination
    
    Ensure that a response is not corrupted as well as incomplete if the
    connection is closed before the response is fully written due to a write
    timeout.
    
    https://bz.apache.org/bugzilla/show_bug.cgi?id=65137
---
 java/org/apache/tomcat/util/net/AprEndpoint.java   | 22 +++++++++++++++++++++-
 java/org/apache/tomcat/util/net/NioEndpoint.java   | 20 +++++++++++++++++++-
 .../apache/tomcat/util/net/SocketWrapperBase.java  |  2 ++
 webapps/docs/changelog.xml                         |  5 +++++
 4 files changed, 47 insertions(+), 2 deletions(-)

diff --git a/java/org/apache/tomcat/util/net/AprEndpoint.java 
b/java/org/apache/tomcat/util/net/AprEndpoint.java
index cf62eab..b388d04 100644
--- a/java/org/apache/tomcat/util/net/AprEndpoint.java
+++ b/java/org/apache/tomcat/util/net/AprEndpoint.java
@@ -2364,6 +2364,25 @@ public class AprEndpoint extends 
AbstractEndpoint<Long,Long> implements SNICallB
 
 
         private void doWriteInternal(ByteBuffer from) throws IOException {
+
+            if (previousIOException != null) {
+                /*
+                 * Socket has previously seen an IOException on write.
+                 *
+                 * Blocking writes assume that buffer is always fully written 
so
+                 * there is no code checking for incomplete writes, retaining
+                 * the unwritten data and attempting to write it as part of a
+                 * subsequent write call.
+                 *
+                 * Because of the above, when an IOException is triggered we
+                 * need so skip subsequent attempts to write as otherwise it
+                 * will appear to the client as if some data was dropped just
+                 * before the connection is lost. It is better if the client
+                 * just sees the dropped connection.
+                 */
+                throw new IOException(previousIOException);
+            }
+
             int thisTime;
 
             do {
@@ -2400,8 +2419,9 @@ public class AprEndpoint extends 
AbstractEndpoint<Long,Long> implements SNICallB
                     // 10053 on Windows is connection aborted
                     throw new 
EOFException(sm.getString("socket.apr.clientAbort"));
                 } else if (thisTime < 0) {
-                    throw new 
IOException(sm.getString("socket.apr.write.error",
+                    previousIOException = new 
IOException(sm.getString("socket.apr.write.error",
                             Integer.valueOf(-thisTime), getSocket(), this));
+                    throw previousIOException;
                 }
             } while ((thisTime > 0 || getBlockingStatus()) && 
from.hasRemaining());
 
diff --git a/java/org/apache/tomcat/util/net/NioEndpoint.java 
b/java/org/apache/tomcat/util/net/NioEndpoint.java
index 9cbf422..5d8db75 100644
--- a/java/org/apache/tomcat/util/net/NioEndpoint.java
+++ b/java/org/apache/tomcat/util/net/NioEndpoint.java
@@ -1305,6 +1305,23 @@ public class NioEndpoint extends 
AbstractJsseEndpoint<NioChannel,SocketChannel>
                 throw new ClosedChannelException();
             }
             if (block) {
+                if (previousIOException != null) {
+                    /*
+                     * Socket has previously timed out.
+                     *
+                     * Blocking writes assume that buffer is always fully
+                     * written so there is no code checking for incomplete
+                     * writes, retaining the unwritten data and attempting to
+                     * write it as part of a subsequent write call.
+                     *
+                     * Because of the above, when a timeout is triggered we 
need
+                     * so skip subsequent attempts to write as otherwise it 
will
+                     * appear to the client as if some data was dropped just
+                     * before the connection is lost. It is better if the 
client
+                     * just sees the dropped connection.
+                     */
+                    throw new IOException(previousIOException);
+                }
                 long timeout = getWriteTimeout();
                 long startNanos = 0;
                 do {
@@ -1315,7 +1332,8 @@ public class NioEndpoint extends 
AbstractJsseEndpoint<NioChannel,SocketChannel>
                         }
                         timeout -= elapsedMillis;
                         if (timeout <= 0) {
-                            throw new SocketTimeoutException();
+                            previousIOException = new SocketTimeoutException();
+                            throw previousIOException;
                         }
                     }
                     n = getSocket().write(buffer);
diff --git a/java/org/apache/tomcat/util/net/SocketWrapperBase.java 
b/java/org/apache/tomcat/util/net/SocketWrapperBase.java
index 06fea5f..0a988aa 100644
--- a/java/org/apache/tomcat/util/net/SocketWrapperBase.java
+++ b/java/org/apache/tomcat/util/net/SocketWrapperBase.java
@@ -51,6 +51,8 @@ public abstract class SocketWrapperBase<E> {
     private volatile long readTimeout = -1;
     private volatile long writeTimeout = -1;
 
+    protected volatile IOException previousIOException = null;
+
     private volatile int keepAliveLeft = 100;
     private String negotiatedProtocol = null;
 
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 653c66e..42ffdbd 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -157,6 +157,11 @@
         entire request body and the server is ready the request body using
         non-blocking I/O. (markt)
       </fix>
+      <fix>
+        <bug>65137</bug>: Ensure that a response is not corrupted as well as
+        incomplete if the connection is closed before the response is fully
+        written due to a write timeout. (markt)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Web applications">


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to