Description: fix denial of service via NIOS HTTP connector
Origin: upstream, http://svn.apache.org/viewvc?view=revision&revision=1066313
Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/tomcat6/+bug/717396
Bug-Ubuntu: https://bugs.edge.launchpad.net/ubuntu/natty/+source/tomcat6/+bug/714239
Bug-Debian: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=612257
Bug: https://issues.apache.org/bugzilla/show_bug.cgi?id=50631

Index: tomcat6-6.0.24/java/org/apache/coyote/http11/InternalNioInputBuffer.java
===================================================================
--- tomcat6-6.0.24.orig/java/org/apache/coyote/http11/InternalNioInputBuffer.java	2009-05-02 21:29:42.000000000 -0400
+++ tomcat6-6.0.24/java/org/apache/coyote/http11/InternalNioInputBuffer.java	2011-03-24 11:08:25.823072700 -0400
@@ -41,6 +41,11 @@
  */
 public class InternalNioInputBuffer implements InputBuffer {
 
+    /**
+     * Logger.
+     */
+    private static final org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog(InternalNioInputBuffer.class);
 
     // -------------------------------------------------------------- Constants
 
@@ -57,12 +62,7 @@
         this.request = request;
         headers = request.getMimeHeaders();
 
-        buf = new byte[headerBufferSize];
-//        if (headerBufferSize < (8 * 1024)) {
-//            bbuf = ByteBuffer.allocateDirect(6 * 1500);
-//        } else {
-//            bbuf = ByteBuffer.allocateDirect((headerBufferSize / 1500 + 1) * 1500);
-//        }
+        this.headerBufferSize = headerBufferSize;
 
         inputStreamInputBuffer = new SocketInputBuffer();
 
@@ -189,6 +189,28 @@
     protected int lastActiveFilter;
 
 
+    /**
+     * Maximum allowed size of the HTTP request line plus headers.
+     */
+    private final int headerBufferSize;
+
+    /**
+     * Known size of the NioChannel read buffer.
+     */
+    private int socketReadBufferSize;
+
+    /**
+     * Additional size we allocate to the buffer to be more effective when
+     * skipping empty lines that may precede the request.
+     */
+    private static final int skipBlankLinesSize = 1024;
+
+    /**
+     * How many bytes in the buffer are occupied by skipped blank lines that
+     * precede the request.
+     */
+    private int skipBlankLinesBytes;
+
     // ------------------------------------------------------------- Properties
 
 
@@ -197,6 +219,12 @@
      */
     public void setSocket(NioChannel socket) {
         this.socket = socket;
+        socketReadBufferSize = socket.getBufHandler().getReadBuffer().capacity();
+        int bufLength = skipBlankLinesSize + headerBufferSize
+                + socketReadBufferSize;
+        if (buf == null || buf.length < bufLength) {
+            buf = new byte[bufLength];
+        }
     }
     
     /**
@@ -421,25 +449,23 @@
                     if (useAvailableData) {
                         return false;
                     }
+                    // Ignore bytes that were read
+                    pos = lastValid = 0;
                     // Do a simple read with a short timeout
                     if ( readSocket(true, false)==0 ) return false;
                 }
                 chr = buf[pos++];
             } while ((chr == Constants.CR) || (chr == Constants.LF));
             pos--;
-            parsingRequestLineStart = pos;
-            parsingRequestLinePhase = 1;
-        } 
-        if ( parsingRequestLinePhase == 1 ) {
-            // Mark the current buffer position
-            
-            if (pos >= lastValid) {
-                if (useAvailableData) {
-                    return false;
-                }
-                // Do a simple read with a short timeout
-                if ( readSocket(true, false)==0 ) return false;
+            if (pos >= skipBlankLinesSize) {
+                // Move data, to have enough space for further reading
+                // of headers and body
+                System.arraycopy(buf, pos, buf, 0, lastValid - pos);
+                lastValid -= pos;
+                pos = 0;
             }
+            skipBlankLinesBytes = pos;
+            parsingRequestLineStart = pos;
             parsingRequestLinePhase = 2;
         }
         if ( parsingRequestLinePhase == 2 ) {
@@ -578,6 +604,13 @@
     
     private void expand(int newsize) {
         if ( newsize > buf.length ) {
+            if (parsingHeader) {
+                throw new IllegalArgumentException(
+                        sm.getString("iib.requestheadertoolarge.error"));
+            }
+            // Should not happen
+            log.warn("Expanding buffer size. Old size: " + buf.length
+                    + ", new size: " + newsize, new Exception());
             byte[] tmp = new byte[newsize];
             System.arraycopy(buf,0,tmp,0,buf.length);
             buf = tmp;
@@ -639,6 +672,19 @@
         if (status == HeaderParseStatus.DONE) {
             parsingHeader = false;
             end = pos;
+            // Checking that
+            // (1) Headers plus request line size does not exceed its limit
+            // (2) There are enough bytes to avoid expanding the buffer when
+            // reading body
+            // Technically, (2) is technical limitation, (1) is logical
+            // limitation to enforce the meaning of headerBufferSize
+            // From the way how buf is allocated and how blank lines are being
+            // read, it should be enough to check (1) only.
+            if (end - skipBlankLinesBytes > headerBufferSize
+                    || buf.length - end < socketReadBufferSize) {
+                throw new IllegalArgumentException(
+                        sm.getString("iib.requestheadertoolarge.error"));
+            }
             return true;
         } else {
             return false;
@@ -888,16 +934,7 @@
             // Do a simple read with a short timeout
             read = readSocket(timeout,block)>0;
         } else {
-
-            if (buf.length - end < 4500) {
-                // In this case, the request header was really large, so we allocate a 
-                // brand new one; the old one will get GCed when subsequent requests
-                // clear all references
-                buf = new byte[buf.length];
-                end = 0;
-            }
-            pos = end;
-            lastValid = pos;
+            lastValid = pos = end;
             // Do a simple read with a short timeout
             read = readSocket(timeout, block)>0;
         }
