diff -Nru tomcat7-7.0.21/debian/changelog tomcat7-7.0.21/debian/changelog --- tomcat7-7.0.21/debian/changelog 2011-09-07 15:47:47.000000000 +0700 +++ tomcat7-7.0.21/debian/changelog 2013-02-06 16:44:57.000000000 +0700 @@ -1,3 +1,34 @@ +tomcat7 (7.0.21-1ubuntu0.1) oneiric-security; urgency=low + + * SECURITY UPDATE: Fix multiple vulnerabilities in Tomcat7 + (LP: #1115053) + - debian/patches/CVE-2012-0022.patch: Fix for Denial of service. Based on + upstream patch. + - CVE-2012-0022, CVE-2011-4858 + - debian/patches/CVE-2011-3375.patch: Fix for information disclosure. Based + on upstream patch. + - CVE-2011-3375 + - debian/patches/CVE-2011-3376.patch: Fix for privilege escalation. Based on + upstream patch. + - CVE-2011-3376 + - debian/patches/CVE-2012-2733.patch: Fix for Apache Tomcat Denial of + Service. Based on upstream patch. + - CVE-2012-2733 + - debian/patches/CVE-2012-3546.patch: Fix for bypass of security + constraints. Based on upstream patch. + - CVE-2012-3546 + - debian/patches/CVE-2012-4431.patch: Fix for bypass of CSRF prevention + filter. Based on upstream patch. + - CVE-2012-4431 + - debian/patches/CVE-2012-4534.patch: Fix for CVE-2012-4534 Denial of + Service Vulnerability. Based on upstream patch. + - CVE-2012-4534 + - debian/patches/CVE-2012-3439.patch: Fix for DIGEST authentication + weaknesses. Based on upstream patch. + - CVE-2012-3439, CVE-2012-5885, CVE-2012-5886, 2012-5887 + + -- Christian Kuersteiner Fri, 01 Feb 2013 14:28:48 +0700 + tomcat7 (7.0.21-1) unstable; urgency=low * New upstream release. diff -Nru tomcat7-7.0.21/debian/control tomcat7-7.0.21/debian/control --- tomcat7-7.0.21/debian/control 2011-09-07 15:47:47.000000000 +0700 +++ tomcat7-7.0.21/debian/control 2013-02-04 16:54:05.000000000 +0700 @@ -1,7 +1,8 @@ Source: tomcat7 Section: java Priority: optional -Maintainer: Debian Java Maintainers +Maintainer: Ubuntu Developers +XSBC-Original-Maintainer: Debian Java Maintainers Uploaders: James Page , Miguel Landaeta , tony mancill Build-Depends: default-jdk, ant-optional, debhelper (>= 7), po-debconf diff -Nru tomcat7-7.0.21/debian/patches/CVE-2011-3375.patch tomcat7-7.0.21/debian/patches/CVE-2011-3375.patch --- tomcat7-7.0.21/debian/patches/CVE-2011-3375.patch 1970-01-01 07:00:00.000000000 +0700 +++ tomcat7-7.0.21/debian/patches/CVE-2011-3375.patch 2013-02-08 13:20:32.000000000 +0700 @@ -0,0 +1,160 @@ +Description: Ensure access log always logs the correct remote IP. +Ensure requests with multiple errors do not result in multiple access log entries. +Origin: upstream +Author: ckuerste@gmx.ch +Bug: http://tomcat.apache.org/security-7.html#Fixed_in_Apache_Tomcat_7.0.22 +Ubuntu-Bug: https://bugs.launchpad.net/ubuntu/+source/tomcat7/+bug/1115053 +--- a/java/org/apache/catalina/connector/CoyoteAdapter.java ++++ b/java/org/apache/catalina/connector/CoyoteAdapter.java +@@ -466,10 +466,8 @@ + + Request request = (Request) req.getNote(ADAPTER_NOTES); + Response response = (Response) res.getNote(ADAPTER_NOTES); +- boolean create = false; + + if (request == null) { +- create = true; + // Create objects + request = connector.createRequest(); + request.setCoyoteRequest(req); +@@ -511,9 +509,7 @@ + } catch (Throwable t) { + ExceptionUtils.handleThrowable(t); + log.warn(sm.getString("coyoteAdapter.accesslogFail"), t); +- } +- +- if (create) { ++ } finally { + request.recycle(); + response.recycle(); + } +--- a/java/org/apache/coyote/ajp/AbstractAjpProcessor.java ++++ b/java/org/apache/coyote/ajp/AbstractAjpProcessor.java +@@ -759,7 +759,6 @@ + secret = true; + if (!tmpMB.equals(requiredSecret)) { + response.setStatus(403); +- adapter.log(request, response, 0); + error = true; + } + } +@@ -776,7 +775,6 @@ + // Check if secret was submitted if required + if ((requiredSecret != null) && !secret) { + response.setStatus(403); +- adapter.log(request, response, 0); + error = true; + } + +@@ -810,6 +808,9 @@ + MessageBytes valueMB = request.getMimeHeaders().getValue("host"); + parseHost(valueMB); + ++ if (error) { ++ adapter.log(request, response, 0); ++ } + } + + +@@ -825,7 +826,6 @@ + request.serverName().duplicate(request.localName()); + } catch (IOException e) { + response.setStatus(400); +- adapter.log(request, response, 0); + error = true; + } + return; +@@ -877,7 +877,6 @@ + error = true; + // 400 - Bad request + response.setStatus(400); +- adapter.log(request, response, 0); + break; + } + port = port + (charValue * mult); +--- a/java/org/apache/coyote/ajp/AjpNioProcessor.java ++++ b/java/org/apache/coyote/ajp/AjpNioProcessor.java +@@ -163,7 +163,7 @@ + } + } + +- if (endpoint.isPaused()) { ++ if (!error && endpoint.isPaused()) { + // 503 - Service unavailable + response.setStatus(503); + adapter.log(request, response, 0); +--- a/java/org/apache/coyote/ajp/AjpProcessor.java ++++ b/java/org/apache/coyote/ajp/AjpProcessor.java +@@ -179,7 +179,7 @@ + } + } + +- if (endpoint.isPaused()) { ++ if (!error && endpoint.isPaused()) { + // 503 - Service unavailable + response.setStatus(503); + adapter.log(request, response, 0); +--- a/java/org/apache/coyote/http11/AbstractHttp11Processor.java ++++ b/java/org/apache/coyote/http11/AbstractHttp11Processor.java +@@ -823,7 +823,6 @@ + " Unsupported HTTP version \""+protocolMB+"\""); + } + response.setStatus(505); +- adapter.log(request, response, 0); + } + + MessageBytes methodMB = request.method(); +@@ -919,7 +918,6 @@ + error = true; + // 501 - Unimplemented + response.setStatus(501); +- adapter.log(request, response, 0); + } + startPos = commaPos + 1; + commaPos = transferEncodingValue.indexOf(',', startPos); +@@ -935,7 +933,6 @@ + " Unsupported transfer encoding \""+encodingName+"\""); + } + response.setStatus(501); +- adapter.log(request, response, 0); + } + } + +@@ -958,7 +955,6 @@ + " host header missing"); + } + response.setStatus(400); +- adapter.log(request, response, 0); + } + + parseHost(valueMB); +@@ -988,6 +984,10 @@ + request.setAttribute("org.apache.tomcat.comet.timeout.support", + Boolean.TRUE); + } ++ ++ if (error) { ++ adapter.log(request, response, 0); ++ } + } + + +@@ -1207,7 +1207,6 @@ + error = true; + // 400 - Bad request + response.setStatus(400); +- adapter.log(request, response, 0); + break; + } + port = port + (charValue * mult); +@@ -1236,8 +1235,9 @@ + } finally { + if (error) { + // 500 - Internal Server Error ++ // Can't add a 500 to the access log since that has already been ++ // written in the Adapter.service method. + response.setStatus(500); +- adapter.log(request, response, 0); + } + } + diff -Nru tomcat7-7.0.21/debian/patches/CVE-2011-3376.patch tomcat7-7.0.21/debian/patches/CVE-2011-3376.patch --- tomcat7-7.0.21/debian/patches/CVE-2011-3376.patch 1970-01-01 07:00:00.000000000 +0700 +++ tomcat7-7.0.21/debian/patches/CVE-2011-3376.patch 2013-02-08 12:27:31.000000000 +0700 @@ -0,0 +1,18 @@ +Description: ContainerServlets are always restricted. +Origin: upstream +Author: ckuerste@gmx.ch +Bug: http://tomcat.apache.org/security-7.html#Fixed_in_Apache_Tomcat_7.0.22 +Ubuntu-Bug: https://bugs.launchpad.net/ubuntu/+source/tomcat7/+bug/1115053 +--- a/java/org/apache/catalina/core/DefaultInstanceManager.java ++++ b/java/org/apache/catalina/core/DefaultInstanceManager.java +@@ -420,6 +420,10 @@ + if (Filter.class.isAssignableFrom(clazz)) { + checkAccess(clazz, restrictedFilters); + } else if (Servlet.class.isAssignableFrom(clazz)) { ++ if (ContainerServlet.class.isAssignableFrom(clazz)) { ++ throw new SecurityException("Restricted (ContainerServlet) " + ++ clazz); ++ } + checkAccess(clazz, restrictedServlets); + } else { + checkAccess(clazz, restrictedListeners); diff -Nru tomcat7-7.0.21/debian/patches/CVE-2012-0022.patch tomcat7-7.0.21/debian/patches/CVE-2012-0022.patch --- tomcat7-7.0.21/debian/patches/CVE-2012-0022.patch 1970-01-01 07:00:00.000000000 +0700 +++ tomcat7-7.0.21/debian/patches/CVE-2012-0022.patch 2013-02-08 12:27:31.000000000 +0700 @@ -0,0 +1,934 @@ +Description: Re-factor parameter parsing to improve performance +Origin: upstream +Author: ckuerste@gmx.ch +Bug: http://tomcat.apache.org/security-7.html#Fixed_in_Apache_Tomcat_7.0.23 +Ubuntu-Bug: https://bugs.launchpad.net/ubuntu/+source/tomcat7/+bug/1115053 +--- a/java/org/apache/catalina/connector/Connector.java ++++ b/java/org/apache/catalina/connector/Connector.java +@@ -172,6 +172,13 @@ + + + /** ++ * The maximum number of parameters (GET plus POST) which will be ++ * automatically parsed by the container. 10000 by default. A value of less ++ * than 0 means no limit. ++ */ ++ protected int maxParameterCount = 10000; ++ ++ /** + * Maximum size of a POST which will be automatically parsed by the + * container. 2MB by default. + */ +@@ -405,12 +412,33 @@ + */ + public Mapper getMapper() { + +- return (mapper); ++ return (mapper); + + } + + + /** ++ * Return the maximum number of parameters (GET plus POST) that will be ++ * automatically parsed by the container. A value of less than 0 means no ++ * limit. ++ */ ++ public int getMaxParameterCount() { ++ return maxParameterCount; ++ } ++ ++ ++ /** ++ * Set the maximum number of parameters (GET plus POST) that will be ++ * automatically parsed by the container. A value less than 0 means no ++ * limit. ++ * ++ * @param maxParameterCount The new setting ++ */ ++ public void setMaxParameterCount(int maxParameterCount) { ++ this.maxParameterCount = maxParameterCount; ++ } ++ ++ /** + * Return the maximum size of a POST which will be automatically + * parsed by the container. + */ +--- a/java/org/apache/catalina/connector/Request.java ++++ b/java/org/apache/catalina/connector/Request.java +@@ -24,6 +24,7 @@ + import java.io.IOException; + import java.io.InputStream; + import java.io.UnsupportedEncodingException; ++import java.nio.charset.Charset; + import java.security.Principal; + import java.text.SimpleDateFormat; + import java.util.ArrayList; +@@ -2656,26 +2657,59 @@ + parts = new ArrayList(); + try { + List items = upload.parseRequest(this); ++ int maxPostSize = getConnector().getMaxPostSize(); ++ int postSize = 0; ++ String enc = getCharacterEncoding(); ++ Charset charset = null; ++ if (enc != null) { ++ try { ++ charset = B2CConverter.getCharset(enc); ++ } catch (UnsupportedEncodingException e) { ++ // Ignore ++ } ++ } + for (FileItem item : items) { + ApplicationPart part = new ApplicationPart(item, mce); + parts.add(part); + if (part.getFilename() == null) { ++ String name = part.getName(); ++ String value = null; + try { + String encoding = parameters.getEncoding(); + if (encoding == null) { + encoding = Parameters.DEFAULT_ENCODING; + } +- parameters.addParameterValues(part.getName(), +- new String[] { part.getString(encoding) }); ++ value = part.getString(encoding); + } catch (UnsupportedEncodingException uee) { + try { +- parameters.addParameterValues(part.getName(), +- new String[] {part.getString( +- Parameters.DEFAULT_ENCODING)}); ++ value = part.getString(Parameters.DEFAULT_ENCODING); + } catch (UnsupportedEncodingException e) { + // Should not be possible + } + } ++ if (maxPostSize > 0) { ++ // Have to calculate equivalent size. Not completely ++ // accurate but close enough. ++ if (charset == null) { ++ // Name length ++ postSize += name.getBytes().length; ++ } else { ++ postSize += name.getBytes(charset).length; ++ } ++ if (value != null) { ++ // Equals sign ++ postSize++; ++ // Value length ++ postSize += part.getSize(); ++ } ++ // Value separator ++ postSize++; ++ if (postSize > maxPostSize) { ++ throw new IllegalStateException(sm.getString( ++ "coyoteRequest.maxPostSizeExceeded")); ++ } ++ } ++ parameters.addParameter(name, value); + } + } + +@@ -2853,6 +2887,8 @@ + parametersParsed = true; + + Parameters parameters = coyoteRequest.getParameters(); ++ // Set this every time in case limit has been changed via JMX ++ parameters.setLimit(getConnector().getMaxParameterCount()); + + // getCharacterEncoding() may have been overridden to search for + // hidden form field containing request encoding +--- a/java/org/apache/catalina/connector/mbeans-descriptors.xml ++++ b/java/org/apache/catalina/connector/mbeans-descriptors.xml +@@ -81,6 +81,10 @@ + description="Maximum number of Keep-Alive requests to honor per connection" + type="int"/> + ++ ++ + +--- a/java/org/apache/tomcat/util/buf/ByteChunk.java ++++ b/java/org/apache/tomcat/util/buf/ByteChunk.java +@@ -19,6 +19,10 @@ + + import java.io.IOException; + import java.io.Serializable; ++import java.io.UnsupportedEncodingException; ++import java.nio.ByteBuffer; ++import java.nio.CharBuffer; ++import java.nio.charset.Charset; + + /* + * In a server it is very important to be able to operate on +@@ -97,15 +101,25 @@ + as most standards seem to converge, but the servlet API requires + 8859_1, and this object is used mostly for servlets. + */ +- public static final String DEFAULT_CHARACTER_ENCODING="ISO-8859-1"; +- ++ public static final Charset DEFAULT_CHARSET; ++ ++ static { ++ Charset c = null; ++ try { ++ c = B2CConverter.getCharset("ISO-8859-1"); ++ } catch (UnsupportedEncodingException e) { ++ // Should never happen since all JVMs must support ISO-8859-1 ++ } ++ DEFAULT_CHARSET = c; ++ } ++ + // byte[] + private byte[] buff; + + private int start=0; + private int end; + +- private String enc; ++ private Charset charset; + + private boolean isSet=false; // XXX + +@@ -146,7 +160,7 @@ + */ + public void recycle() { + // buff = null; +- enc=null; ++ charset=null; + start=0; + end=0; + isSet=false; +@@ -186,13 +200,15 @@ + this.optimizedWrite = optimizedWrite; + } + +- public void setEncoding( String enc ) { +- this.enc=enc; ++ public void setCharset(Charset charset) { ++ this.charset = charset; + } +- public String getEncoding() { +- if (enc == null) +- enc=DEFAULT_CHARACTER_ENCODING; +- return enc; ++ ++ public Charset getCharset() { ++ if (charset == null) { ++ charset = DEFAULT_CHARSET; ++ } ++ return charset; + } + + /** +@@ -497,31 +513,15 @@ + } + + public String toStringInternal() { +- String strValue=null; +- try { +- if (enc == null) { +- enc = DEFAULT_CHARACTER_ENCODING; +- } +- strValue = new String(buff, start, end-start, +- B2CConverter.getCharset(enc)); +- /* +- Does not improve the speed too much on most systems, +- it's safer to use the "classical" new String(). +- +- Most overhead is in creating char[] and copying, +- the internal implementation of new String() is very close to +- what we do. The decoder is nice for large buffers and if +- we don't go to String ( so we can take advantage of reduced GC) +- +- // Method is commented out, in: +- return B2CConverter.decodeString( enc ); +- */ +- } catch (java.io.UnsupportedEncodingException e) { +- // Use the platform encoding in that case; the usage of a bad +- // encoding will have been logged elsewhere already +- strValue = new String(buff, start, end-start); ++ if (charset == null) { ++ charset = DEFAULT_CHARSET; + } +- return strValue; ++ // new String(byte[], int, int, Charset) takes a defensive copy of the ++ // entire byte array. This is expensive if only a small subset of the ++ // bytes will be used. The code below is from Apache Harmony. ++ CharBuffer cb; ++ cb = charset.decode(ByteBuffer.wrap(buff, start, end-start)); ++ return new String(cb.array(), cb.arrayOffset(), cb.length()); + } + + public int getInt() +--- a/java/org/apache/tomcat/util/buf/MessageBytes.java ++++ b/java/org/apache/tomcat/util/buf/MessageBytes.java +@@ -130,13 +130,13 @@ + * previous conversion is reset. + * If no encoding is set, we'll use 8859-1. + */ +- public void setEncoding( String enc ) { ++ public void setCharset(Charset charset) { + if( !byteC.isNull() ) { + // if the encoding changes we need to reset the conversion results + charC.recycle(); + hasStrValue=false; + } +- byteC.setEncoding(enc); ++ byteC.setCharset(charset); + } + + /** +--- a/java/org/apache/tomcat/util/buf/StringCache.java ++++ b/java/org/apache/tomcat/util/buf/StringCache.java +@@ -17,6 +17,7 @@ + + package org.apache.tomcat.util.buf; + ++import java.nio.charset.Charset; + import java.util.ArrayList; + import java.util.HashMap; + import java.util.Map.Entry; +@@ -299,7 +300,7 @@ + System.arraycopy(bc.getBuffer(), start, entry.name, + 0, end - start); + // Set encoding +- entry.enc = bc.getEncoding(); ++ entry.charset = bc.getCharset(); + // Initialize occurrence count to one + count = new int[1]; + count[0] = 1; +@@ -485,7 +486,7 @@ + protected static final String find(ByteChunk name) { + int pos = findClosest(name, bcCache, bcCache.length); + if ((pos < 0) || (compare(name, bcCache[pos].name) != 0) +- || !(name.getEncoding().equals(bcCache[pos].enc))) { ++ || !(name.getCharset().equals(bcCache[pos].charset))) { + return null; + } else { + return bcCache[pos].value; +@@ -640,7 +641,7 @@ + public static class ByteEntry { + + public byte[] name = null; +- public String enc = null; ++ public Charset charset = null; + public String value = null; + + @Override +--- /dev/null ++++ b/java/org/apache/tomcat/util/http/LocalStrings.properties +@@ -0,0 +1,23 @@ ++# Licensed to the Apache Software Foundation (ASF) under one or more ++# contributor license agreements. See the NOTICE file distributed with ++# this work for additional information regarding copyright ownership. ++# The ASF licenses this file to You under the Apache License, Version 2.0 ++# (the "License"); you may not use this file except in compliance with ++# the License. You may obtain a copy of the License at ++# ++# http://www.apache.org/licenses/LICENSE-2.0 ++# ++# Unless required by applicable law or agreed to in writing, software ++# distributed under the License is distributed on an "AS IS" BASIS, ++# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++# See the License for the specific language governing permissions and ++# limitations under the License. ++ ++parameters.bytes=Start processing with input [{0}] ++paramerers.copyFail=Failed to create copy of original parameter values for debug logging purposes ++parameters.decodeFail.debug=Character decoding failed. Parameter [{0}] with value [{1}] has been ignored. ++parameters.decodeFail.info=Character decoding failed. Parameter [{0}] with value [{1}] has been ignored. Note that the name and value quoted here may be corrupted due to the failed decoding. Use debug level logging to see the original, non-corrupted values. ++parameters.invalidChunk=Invalid chunk starting at byte [{0}] and ending at byte [{1}] with a value of [{2}] ignored ++parameters.maxCountFail=More than the maximum number of request parameters (GET plus POST) for a single request ([{0}]) were detected. Any parameters beyond this limit have been ignored. To change this limit, set the maxParameterCount attribute on the Connector. ++parameters.multipleDecodingFail=Character decoding failed. A total of [{0}] failures were detected but only the first was logged. Enable debug level logging for this logger to log all failures. ++parameters.noequal=Parameter starting at position [{0}] and ending at position [{1}] with a value of [{0}] was not followed by an '=' character +--- a/java/org/apache/tomcat/util/http/Parameters.java ++++ b/java/org/apache/tomcat/util/http/Parameters.java +@@ -18,14 +18,20 @@ + package org.apache.tomcat.util.http; + + import java.io.IOException; ++import java.io.UnsupportedEncodingException; + import java.nio.charset.Charset; ++import java.util.ArrayList; ++import java.util.Collections; + import java.util.Enumeration; +-import java.util.Hashtable; ++import java.util.HashMap; ++import java.util.Map; + ++import org.apache.tomcat.util.buf.B2CConverter; + import org.apache.tomcat.util.buf.ByteChunk; + import org.apache.tomcat.util.buf.CharChunk; + import org.apache.tomcat.util.buf.MessageBytes; + import org.apache.tomcat.util.buf.UDecoder; ++import org.apache.tomcat.util.res.StringManager; + + /** + * +@@ -34,14 +40,15 @@ + public final class Parameters { + + +- private static final org.apache.juli.logging.Log log= ++ private static final org.apache.juli.logging.Log log = + org.apache.juli.logging.LogFactory.getLog(Parameters.class ); +- +- // Transition: we'll use the same Hashtable( String->String[] ) +- // for the beginning. When we are sure all accesses happen through +- // this class - we can switch to MultiMap +- private Hashtable paramHashStringArray = +- new Hashtable(); ++ ++ protected static final StringManager sm = ++ StringManager.getManager("org.apache.tomcat.util.http"); ++ ++ private final HashMap> paramHashValues = ++ new HashMap>(); ++ + private boolean didQueryParameters=false; + + MessageBytes queryMB; +@@ -51,7 +58,10 @@ + + String encoding=null; + String queryStringEncoding=null; +- ++ ++ private int limit = -1; ++ private int parameterCount = 0; ++ + public Parameters() { + // NO-OP + } +@@ -60,6 +70,10 @@ + this.queryMB=queryMB; + } + ++ public void setLimit(int limit) { ++ this.limit = limit; ++ } ++ + public String getEncoding() { + return encoding; + } +@@ -79,7 +93,8 @@ + } + + public void recycle() { +- paramHashStringArray.clear(); ++ parameterCount = 0; ++ paramHashValues.clear(); + didQueryParameters=false; + encoding=null; + decodedQuery.recycle(); +@@ -88,46 +103,47 @@ + // -------------------- Data access -------------------- + // Access to the current name/values, no side effect ( processing ). + // You must explicitly call handleQueryParameters and the post methods. +- +- // This is the original data representation ( hash of String->String[]) + +- public void addParameterValues( String key, String[] newValues) { +- if ( key==null ) return; +- String values[]; +- if (paramHashStringArray.containsKey(key)) { +- String oldValues[] = paramHashStringArray.get(key); +- values = new String[oldValues.length + newValues.length]; +- for (int i = 0; i < oldValues.length; i++) { +- values[i] = oldValues[i]; +- } +- for (int i = 0; i < newValues.length; i++) { +- values[i+ oldValues.length] = newValues[i]; +- } ++ @Deprecated ++ public void addParameterValues(String key, String[] newValues) { ++ if (key == null) { ++ return; ++ } ++ ArrayList values = paramHashValues.get(key); ++ if (values == null) { ++ values = new ArrayList(newValues.length); ++ paramHashValues.put(key, values); + } else { +- values = newValues; ++ values.ensureCapacity(values.size() + newValues.length); ++ } ++ for (String newValue : newValues) { ++ values.add(newValue); + } +- +- paramHashStringArray.put(key, values); + } + + public String[] getParameterValues(String name) { + handleQueryParameters(); + // no "facade" +- String values[] = paramHashStringArray.get(name); +- return values; ++ ArrayList values = paramHashValues.get(name); ++ if (values == null) { ++ return null; ++ } ++ return values.toArray(new String[values.size()]); + } + + public Enumeration getParameterNames() { + handleQueryParameters(); +- return paramHashStringArray.keys(); ++ return Collections.enumeration(paramHashValues.keySet()); + } + +- // Shortcut. + public String getParameter(String name ) { +- String[] values = getParameterValues(name); ++ handleQueryParameters(); ++ ArrayList values = paramHashValues.get(name); + if (values != null) { +- if( values.length==0 ) return ""; +- return values[0]; ++ if (values.size() == 0) { ++ return ""; ++ } ++ return values.get(0); + } else { + return null; + } +@@ -157,25 +173,26 @@ + processParameters( decodedQuery, queryStringEncoding ); + } + +- // incredibly inefficient data representation for parameters, +- // until we test the new one +- private void addParam( String key, String value ) { ++ ++ public void addParameter( String key, String value ) ++ throws IllegalStateException { ++ + if( key==null ) return; +- String values[]; +- if (paramHashStringArray.containsKey(key)) { +- String oldValues[] = paramHashStringArray.get(key); +- values = new String[oldValues.length + 1]; +- for (int i = 0; i < oldValues.length; i++) { +- values[i] = oldValues[i]; +- } +- values[oldValues.length] = value; +- } else { +- values = new String[1]; +- values[0] = value; ++ ++ parameterCount ++; ++ if (limit > -1 && parameterCount > limit) { ++ // Processing this parameter will push us over the limit. ISE is ++ // what Request.parseParts() uses for requests that are too big ++ throw new IllegalStateException(sm.getString( ++ "parameters.maxCountFail", Integer.valueOf(limit))); + } +- +- +- paramHashStringArray.put(key, values); ++ ++ ArrayList values = paramHashValues.get(key); ++ if (values == null) { ++ values = new ArrayList(1); ++ paramHashValues.put(key, values); ++ } ++ values.add(value); + } + + public void setURLDecoder( UDecoder u ) { +@@ -191,109 +208,162 @@ + private ByteChunk origValue=new ByteChunk(); + CharChunk tmpNameC=new CharChunk(1024); + public static final String DEFAULT_ENCODING = "ISO-8859-1"; +- public static final Charset DEFAULT_CHARSET = ++ private static final Charset DEFAULT_CHARSET = + Charset.forName(DEFAULT_ENCODING); + + + public void processParameters( byte bytes[], int start, int len ) { +- processParameters(bytes, start, len, encoding); ++ processParameters(bytes, start, len, getCharset(encoding)); + } + +- public void processParameters( byte bytes[], int start, int len, +- String enc ) { +- int end=start+len; +- int pos=start; +- ++ private void processParameters(byte bytes[], int start, int len, ++ Charset charset) { ++ + if(log.isDebugEnabled()) { +- log.debug("Bytes: " + +- new String(bytes, start, len, DEFAULT_CHARSET)); ++ log.debug(sm.getString("parameters.bytes", ++ new String(bytes, start, len, DEFAULT_CHARSET))); + } + +- do { +- boolean noEq=false; +- int valStart=-1; +- int valEnd=-1; +- +- int nameStart=pos; +- int nameEnd=ByteChunk.indexOf(bytes, nameStart, end, '=' ); +- // Workaround for a&b&c encoding +- int nameEnd2=ByteChunk.indexOf(bytes, nameStart, end, '&' ); +- if( (nameEnd2!=-1 ) && +- ( nameEnd==-1 || nameEnd > nameEnd2) ) { +- nameEnd=nameEnd2; +- noEq=true; +- valStart=nameEnd; +- valEnd=nameEnd; +- if(log.isDebugEnabled()) { +- log.debug("no equal " + nameStart + " " + nameEnd + " " + +- new String(bytes, nameStart, nameEnd-nameStart, +- DEFAULT_CHARSET)); ++ int decodeFailCount = 0; ++ int pos = start; ++ int end = start + len; ++ ++ while(pos < end) { ++ int nameStart = pos; ++ int nameEnd = -1; ++ int valueStart = -1; ++ int valueEnd = -1; ++ ++ boolean parsingName = true; ++ boolean decodeName = false; ++ boolean decodeValue = false; ++ boolean parameterComplete = false; ++ ++ do { ++ switch(bytes[pos]) { ++ case '=': ++ if (parsingName) { ++ // Name finished. Value starts from next character ++ nameEnd = pos; ++ parsingName = false; ++ valueStart = ++pos; ++ } else { ++ // Equals character in value ++ pos++; ++ } ++ break; ++ case '&': ++ if (parsingName) { ++ // Name finished. No value. ++ nameEnd = pos; ++ } else { ++ // Value finished ++ valueEnd = pos; ++ } ++ parameterComplete = true; ++ pos++; ++ break; ++ case '%': ++ // Decoding required ++ if (parsingName) { ++ decodeName = true; ++ } else { ++ decodeValue = true; ++ } ++ pos++; ++ break; ++ default: ++ pos++; ++ break; + } +- } +- if( nameEnd== -1 ) +- nameEnd=end; ++ } while (!parameterComplete && pos < end); + +- if( ! noEq ) { +- valStart= (nameEnd < end) ? nameEnd+1 : end; +- valEnd=ByteChunk.indexOf(bytes, valStart, end, '&'); +- if( valEnd== -1 ) valEnd = (valStart < end) ? end : valStart; ++ if (pos == end) { ++ if (nameEnd == -1) { ++ nameEnd = pos; ++ } else if (valueStart > -1 && valueEnd == -1){ ++ valueEnd = pos; ++ } + } + +- pos=valEnd+1; +- +- if( nameEnd<=nameStart ) { ++ if (log.isDebugEnabled() && valueStart == -1) { ++ log.debug(sm.getString("parameters.noequal", ++ Integer.valueOf(nameStart), Integer.valueOf(nameEnd), ++ new String(bytes, nameStart, nameEnd-nameStart, ++ DEFAULT_CHARSET))); ++ } ++ ++ if (nameEnd <= nameStart) { + if (log.isInfoEnabled()) { +- StringBuilder msg = new StringBuilder("Parameters: Invalid chunk "); +- // No name eg ...&=xx&... will trigger this +- if (valEnd >= nameStart) { +- msg.append('\''); +- msg.append(new String(bytes, nameStart, +- valEnd - nameStart, DEFAULT_CHARSET)); +- msg.append("' "); ++ String extract; ++ if (valueEnd >= nameStart) { ++ extract = new String(bytes, nameStart, ++ valueEnd - nameStart, DEFAULT_CHARSET); ++ log.info(sm.getString("parameters.invalidChunk", ++ Integer.valueOf(nameStart), ++ Integer.valueOf(valueEnd), ++ extract)); ++ } else { ++ log.info(sm.getString("parameters.invalidChunk", ++ Integer.valueOf(nameStart), ++ Integer.valueOf(nameEnd), ++ null)); + } +- msg.append("ignored."); +- log.info(msg); + } + continue; + // invalid chunk - it's better to ignore + } +- tmpName.setBytes( bytes, nameStart, nameEnd-nameStart ); +- tmpValue.setBytes( bytes, valStart, valEnd-valStart ); +- ++ ++ tmpName.setBytes(bytes, nameStart, nameEnd - nameStart); ++ tmpValue.setBytes(bytes, valueStart, valueEnd - valueStart); ++ + // Take copies as if anything goes wrong originals will be + // corrupted. This means original values can be logged. + // For performance - only done for debug + if (log.isDebugEnabled()) { + try { +- origName.append(bytes, nameStart, nameEnd-nameStart); +- origValue.append(bytes, valStart, valEnd-valStart); ++ origName.append(bytes, nameStart, nameEnd - nameStart); ++ origValue.append(bytes, valueStart, valueEnd - valueStart); + } catch (IOException ioe) { + // Should never happen... +- log.error("Error copying parameters", ioe); ++ log.error(sm.getString("paramerers.copyFail"), ioe); + } + } + + try { +- addParam( urlDecode(tmpName, enc), urlDecode(tmpValue, enc) ); ++ String name; ++ String value; ++ ++ if (decodeName) { ++ urlDecode(tmpName); ++ } ++ tmpName.setCharset(charset); ++ name = tmpName.toString(); ++ ++ if (decodeValue) { ++ urlDecode(tmpValue); ++ } ++ tmpValue.setCharset(charset); ++ value = tmpValue.toString(); ++ ++ try { ++ addParameter(name, value); ++ } catch (IllegalStateException ise) { ++ // Hitting limit stops processing further params but does ++ // not cause request to fail. ++ log.warn(ise.getMessage()); ++ break; ++ } + } catch (IOException e) { +- StringBuilder msg = +- new StringBuilder("Parameters: Character decoding failed."); +- msg.append(" Parameter '"); +- if (log.isDebugEnabled()) { +- msg.append(origName.toString()); +- msg.append("' with value '"); +- msg.append(origValue.toString()); +- msg.append("' has been ignored."); +- log.debug(msg, e); +- } else if (log.isInfoEnabled()) { +- msg.append(tmpName.toString()); +- msg.append("' with value '"); +- msg.append(tmpValue.toString()); +- msg.append("' has been ignored. Note that the name and "); +- msg.append("value quoted here may be corrupted due to "); +- msg.append("the failed decoding. Use debug level logging "); +- msg.append("to see the original, non-corrupted values."); +- log.info(msg); ++ decodeFailCount++; ++ if (decodeFailCount == 1 || log.isDebugEnabled()) { ++ if (log.isDebugEnabled()) { ++ log.debug(sm.getString("parameters.decodeFail.debug", ++ origName.toString(), origValue.toString()), e); ++ } else if (log.isInfoEnabled()) { ++ log.info(sm.getString("parameters.decodeFail.info", ++ tmpName.toString(), tmpValue.toString()), e); ++ } + } + } + +@@ -304,35 +374,20 @@ + origName.recycle(); + origValue.recycle(); + } +- } while( pos 1 && !log.isDebugEnabled()) { ++ log.info(sm.getString("parameters.multipleDecodingFail", ++ Integer.valueOf(decodeFailCount))); ++ } + } + +- private String urlDecode(ByteChunk bc, String enc) ++ private void urlDecode(ByteChunk bc) + throws IOException { + if( urlDec==null ) { + urlDec=new UDecoder(); + } + urlDec.convert(bc); +- String result = null; +- if (enc != null) { +- bc.setEncoding(enc); +- result = bc.toString(); +- } else { +- CharChunk cc = tmpNameC; +- int length = bc.getLength(); +- cc.allocate(length, -1); +- // Default encoding: fast conversion +- byte[] bbuf = bc.getBuffer(); +- char[] cbuf = cc.getBuffer(); +- int start = bc.getStart(); +- for (int i = 0; i < length; i++) { +- cbuf[i] = (char) (bbuf[i + start] & 0xff); +- } +- cc.setChars(cbuf, 0, length); +- result = cc.toString(); +- cc.recycle(); +- } +- return result; + } + + public void processParameters( MessageBytes data, String encoding ) { +@@ -343,23 +398,33 @@ + } + ByteChunk bc=data.getByteChunk(); + processParameters( bc.getBytes(), bc.getOffset(), +- bc.getLength(), encoding); ++ bc.getLength(), getCharset(encoding)); + } + +- /** Debug purpose ++ private Charset getCharset(String encoding) { ++ if (encoding == null) { ++ return DEFAULT_CHARSET; ++ } ++ try { ++ return B2CConverter.getCharset(encoding); ++ } catch (UnsupportedEncodingException e) { ++ return DEFAULT_CHARSET; ++ } ++ } ++ ++ /** ++ * Debug purpose + */ + public String paramsAsString() { +- StringBuilder sb=new StringBuilder(); +- Enumeration en= paramHashStringArray.keys(); +- while( en.hasMoreElements() ) { +- String k = en.nextElement(); +- sb.append( k ).append("="); +- String v[] = paramHashStringArray.get( k ); +- for( int i=0; i> e : paramHashValues.entrySet()) { ++ sb.append(e.getKey()).append('='); ++ ArrayList values = e.getValue(); ++ for (String value : values) { ++ sb.append(value).append(','); ++ } ++ sb.append('\n'); + } + return sb.toString(); + } +- + } +--- a/webapps/docs/config/ajp.xml ++++ b/webapps/docs/config/ajp.xml +@@ -95,6 +95,12 @@ + By default, DNS lookups are disabled.

+
+ ++ ++

The maximum number of parameters (GET plus POST) which will be ++ automatically parsed by the container. A value of less than 0 means no ++ limit. If not specified, a default of 10000 is used.

++
++ + +

The maximum size in bytes of the POST which will be handled by + the container FORM URL parameter parsing. The limit can be disabled by +--- a/webapps/docs/config/http.xml ++++ b/webapps/docs/config/http.xml +@@ -93,6 +93,12 @@ + By default, DNS lookups are disabled.

+
+ ++ ++

The maximum number of parameters (GET plus POST) which will be ++ automatically parsed by the container. A value of less than 0 means no ++ limit. If not specified, a default of 10000 is used.

++
++ + +

The maximum size in bytes of the POST which will be handled by + the container FORM URL parameter parsing. The limit can be disabled by +--- a/java/org/apache/tomcat/util/buf/B2CConverter.java ++++ b/java/org/apache/tomcat/util/buf/B2CConverter.java +@@ -106,14 +106,22 @@ + static final int BUFFER_SIZE=8192; + char result[]=new char[BUFFER_SIZE]; + +- public void convert( ByteChunk bb, CharChunk cb, int limit) ++ /** ++ * Convert a buffer of bytes into a chars. ++ * ++ * @param bb Input byte buffer ++ * @param cb Output char buffer ++ * @param limit Number of bytes to convert ++ * @throws IOException ++ */ ++ public void convert( ByteChunk bb, CharChunk cb, int limit) + throws IOException + { + iis.setByteChunk( bb ); + try { + // read from the reader + int bbLengthBeforeRead = 0; +- while( limit > 0 ) { // conv.ready() ) { ++ while( limit > 0 ) { + int size = limit < BUFFER_SIZE ? limit : BUFFER_SIZE; + bbLengthBeforeRead = bb.getLength(); + int cnt=conv.read( result, 0, size ); +--- a/java/org/apache/catalina/connector/LocalStrings.properties ++++ b/java/org/apache/catalina/connector/LocalStrings.properties +@@ -69,6 +69,7 @@ + coyoteRequest.uploadLocationInvalid=The temporary upload location [{0}] is not valid + coyoteRequest.sessionEndAccessFail=Exception triggered ending access to session while recycling request + coyoteRequest.sendfileNotCanonical=Unable to determine canonical name of file [{0}] specified for use with sendfile ++coyoteRequest.maxPostSizeExceeded=The multi-part request contained parameter data (excluding uploaded files) that exceeded the limit for maxPostSize set on the associated connector + + requestFacade.nullRequest=The request object has been recycled and is no longer associated with this facade + diff -Nru tomcat7-7.0.21/debian/patches/CVE-2012-2733.patch tomcat7-7.0.21/debian/patches/CVE-2012-2733.patch --- tomcat7-7.0.21/debian/patches/CVE-2012-2733.patch 1970-01-01 07:00:00.000000000 +0700 +++ tomcat7-7.0.21/debian/patches/CVE-2012-2733.patch 2013-02-08 12:27:31.000000000 +0700 @@ -0,0 +1,36 @@ +Description: Improve InternalNioInputBuffer#parseHeaders() +Origin: upstream +Author: ckuerste@gmx.ch +Bug: http://tomcat.apache.org/security-7.html#Fixed_in_Apache_Tomcat_7.0.28 +Ubuntu-Bug: https://bugs.launchpad.net/ubuntu/+source/tomcat7/+bug/1115053 +--- a/java/org/apache/coyote/http11/InternalNioInputBuffer.java ++++ b/java/org/apache/coyote/http11/InternalNioInputBuffer.java +@@ -433,10 +433,6 @@ + + do { + status = parseHeader(); +- } while ( status == HeaderParseStatus.HAVE_MORE_HEADERS ); +- 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 +@@ -445,11 +441,15 @@ + // 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) { ++ if (pos - skipBlankLinesBytes > headerBufferSize ++ || buf.length - pos < socketReadBufferSize) { + throw new IllegalArgumentException( + sm.getString("iib.requestheadertoolarge.error")); + } ++ } while ( status == HeaderParseStatus.HAVE_MORE_HEADERS ); ++ if (status == HeaderParseStatus.DONE) { ++ parsingHeader = false; ++ end = pos; + return true; + } else { + return false; diff -Nru tomcat7-7.0.21/debian/patches/CVE-2012-3439.patch tomcat7-7.0.21/debian/patches/CVE-2012-3439.patch --- tomcat7-7.0.21/debian/patches/CVE-2012-3439.patch 1970-01-01 07:00:00.000000000 +0700 +++ tomcat7-7.0.21/debian/patches/CVE-2012-3439.patch 2013-02-08 12:27:31.000000000 +0700 @@ -0,0 +1,396 @@ +Description: Digest improvements: disable caching of authenticated user in session by default, +track server rather than client nonces, better handling of stale nonce values +Origin: upstream +Author: ckuerste@gmx.ch +Bug: http://tomcat.apache.org/security-7.html#Fixed_in_Apache_Tomcat_7.0.30 +Ubuntu-Bug: https://bugs.launchpad.net/ubuntu/+source/tomcat7/+bug/1115053 +--- a/java/org/apache/catalina/authenticator/DigestAuthenticator.java ++++ b/java/org/apache/catalina/authenticator/DigestAuthenticator.java +@@ -20,7 +20,6 @@ + + + import java.io.IOException; +-import java.nio.charset.Charset; + import java.security.MessageDigest; + import java.security.NoSuchAlgorithmException; + import java.security.Principal; +@@ -38,6 +37,7 @@ + import org.apache.catalina.util.MD5Encoder; + import org.apache.juli.logging.Log; + import org.apache.juli.logging.LogFactory; ++import org.apache.tomcat.util.buf.B2CConverter; + + + +@@ -80,6 +80,7 @@ + + public DigestAuthenticator() { + super(); ++ setCache(false); + try { + if (md5Helper == null) + md5Helper = MessageDigest.getInstance("MD5"); +@@ -100,16 +101,16 @@ + + + /** +- * List of client nonce values currently being tracked ++ * List of server nonce values currently being tracked + */ +- protected Map cnonces; ++ protected Map nonces; + + + /** +- * Maximum number of client nonces to keep in the cache. If not specified, ++ * Maximum number of server nonces to keep in the cache. If not specified, + * the default value of 1000 is used. + */ +- protected int cnonceCacheSize = 1000; ++ protected int nonceCacheSize = 1000; + + + /** +@@ -150,13 +151,13 @@ + } + + +- public int getCnonceCacheSize() { +- return cnonceCacheSize; ++ public int getNonceCacheSize() { ++ return nonceCacheSize; + } + + +- public void setCnonceCacheSize(int cnonceCacheSize) { +- this.cnonceCacheSize = cnonceCacheSize; ++ public void setNonceCacheSize(int nonceCacheSize) { ++ this.nonceCacheSize = nonceCacheSize; + } + + +@@ -263,19 +264,20 @@ + // Validate any credentials already included with this request + String authorization = request.getHeader("authorization"); + DigestInfo digestInfo = new DigestInfo(getOpaque(), getNonceValidity(), +- getKey(), cnonces, isValidateUri()); ++ getKey(), nonces, isValidateUri()); + if (authorization != null) { +- if (digestInfo.validate(request, authorization, config)) { +- principal = digestInfo.authenticate(context.getRealm()); +- } ++ if (digestInfo.parse(request, authorization)) { ++ if (digestInfo.validate(request, config)) { ++ principal = digestInfo.authenticate(context.getRealm()); ++ } + +- if (principal != null) { +- String username = parseUsername(authorization); +- register(request, response, principal, +- Constants.DIGEST_METHOD, +- username, null); +- return (true); +- } ++ if (principal != null && !digestInfo.isNonceStale()) { ++ register(request, response, principal, ++ HttpServletRequest.DIGEST_AUTH, ++ digestInfo.getUsername(), null); ++ return true; ++ } ++ } + } + + // Send an "unauthorized" response and an appropriate challenge +@@ -285,11 +287,9 @@ + String nonce = generateNonce(request); + + setAuthenticateHeader(request, response, config, nonce, +- digestInfo.isNonceStale()); ++ principal != null && digestInfo.isNonceStale()); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); +- // hres.flushBuffer(); +- return (false); +- ++ return false; + } + + +@@ -380,10 +380,17 @@ + byte[] buffer; + synchronized (md5Helper) { + buffer = md5Helper.digest( +- ipTimeKey.getBytes(Charset.defaultCharset())); ++ ipTimeKey.getBytes(B2CConverter.ISO_8859_1)); + } + +- return currentTime + ":" + md5Encoder.encode(buffer); ++ String nonce = currentTime + ":" + MD5Encoder.encode(buffer); ++ ++ NonceInfo info = new NonceInfo(currentTime, 100); ++ synchronized (nonces) { ++ nonces.put(nonce, info); ++ } ++ ++ return nonce; + } + + +@@ -457,7 +464,7 @@ + setOpaque(sessionIdGenerator.generateSessionId()); + } + +- cnonces = new LinkedHashMap() { ++ nonces = new LinkedHashMap() { + + private static final long serialVersionUID = 1L; + private static final long LOG_SUPPRESS_TIME = 5 * 60 * 1000; +@@ -469,7 +476,7 @@ + Map.Entry eldest) { + // This is called from a sync so keep it simple + long currentTime = System.currentTimeMillis(); +- if (size() > getCnonceCacheSize()) { ++ if (size() > getNonceCacheSize()) { + if (lastLog < currentTime && + currentTime - eldest.getValue().getTimestamp() < + getNonceValidity()) { +@@ -487,10 +494,10 @@ + + private static class DigestInfo { + +- private String opaque; +- private long nonceValidity; +- private String key; +- private Map cnonces; ++ private final String opaque; ++ private final long nonceValidity; ++ private final String key; ++ private final Map nonces; + private boolean validateUri = true; + + private String userName = null; +@@ -502,21 +509,25 @@ + private String cnonce = null; + private String realmName = null; + private String qop = null; ++ private String opaqueReceived = null; + + private boolean nonceStale = false; + + + public DigestInfo(String opaque, long nonceValidity, String key, +- Map cnonces, boolean validateUri) { ++ Map nonces, boolean validateUri) { + this.opaque = opaque; + this.nonceValidity = nonceValidity; + this.key = key; +- this.cnonces = cnonces; ++ this.nonces = nonces; + this.validateUri = validateUri; + } + +- public boolean validate(Request request, String authorization, +- LoginConfig config) { ++ public String getUsername() { ++ return userName; ++ } ++ ++ public boolean parse(Request request, String authorization) { + // Validate the authorization credentials format + if (authorization == null) { + return false; +@@ -530,7 +541,6 @@ + String[] tokens = authorization.split(",(?=(?:[^\"]*\"[^\"]*\")+$)"); + + method = request.getMethod(); +- String opaque = null; + + for (int i = 0; i < tokens.length; i++) { + String currentToken = tokens[i]; +@@ -562,9 +572,13 @@ + if ("response".equals(currentTokenName)) + response = removeQuotes(currentTokenValue); + if ("opaque".equals(currentTokenName)) +- opaque = removeQuotes(currentTokenValue); ++ opaqueReceived = removeQuotes(currentTokenValue); + } + ++ return true; ++ } ++ ++ public boolean validate(Request request, LoginConfig config) { + if ( (userName == null) || (realmName == null) || (nonce == null) + || (uri == null) || (response == null) ) { + return false; +@@ -613,14 +627,16 @@ + long currentTime = System.currentTimeMillis(); + if ((currentTime - nonceTime) > nonceValidity) { + nonceStale = true; +- return false; ++ synchronized (nonces) { ++ nonces.remove(nonce); ++ } + } + String serverIpTimeKey = + request.getRemoteAddr() + ":" + nonceTime + ":" + key; + byte[] buffer = null; + synchronized (md5Helper) { + buffer = md5Helper.digest( +- serverIpTimeKey.getBytes(Charset.defaultCharset())); ++ serverIpTimeKey.getBytes(B2CConverter.ISO_8859_1)); + } + String md5ServerIpTimeKey = md5Encoder.encode(buffer); + if (!md5ServerIpTimeKey.equals(md5clientIpTimeKey)) { +@@ -633,7 +649,7 @@ + } + + // Validate cnonce and nc +- // Check if presence of nc and nonce is consistent with presence of qop ++ // Check if presence of nc and Cnonce is consistent with presence of qop + if (qop == null) { + if (cnonce != null || nc != null) { + return false; +@@ -652,21 +668,18 @@ + return false; + } + NonceInfo info; +- synchronized (cnonces) { +- info = cnonces.get(cnonce); ++ synchronized (nonces) { ++ info = nonces.get(nonce); + } + if (info == null) { +- info = new NonceInfo(); ++ // Nonce is valid but not in cache. It must have dropped out ++ // of the cache - force a re-authentication ++ nonceStale = true; + } else { +- if (count <= info.getCount()) { ++ if (!info.nonceCountValid(count)) { + return false; + } + } +- info.setCount(count); +- info.setTimestamp(currentTime); +- synchronized (cnonces) { +- cnonces.put(cnonce, info); +- } + } + return true; + } +@@ -682,7 +695,7 @@ + + byte[] buffer; + synchronized (md5Helper) { +- buffer = md5Helper.digest(a2.getBytes(Charset.defaultCharset())); ++ buffer = md5Helper.digest(a2.getBytes(B2CConverter.ISO_8859_1)); + } + String md5a2 = md5Encoder.encode(buffer); + +@@ -693,19 +706,31 @@ + } + + private static class NonceInfo { +- private volatile long count; + private volatile long timestamp; +- +- public void setCount(long l) { +- count = l; +- } +- +- public long getCount() { +- return count; ++ private volatile boolean seen[]; ++ private volatile int offset; ++ private volatile int count = 0; ++ ++ public NonceInfo(long currentTime, int seenWindowSize) { ++ this.timestamp = currentTime; ++ seen = new boolean[seenWindowSize]; ++ offset = seenWindowSize / 2; + } +- +- public void setTimestamp(long l) { +- timestamp = l; ++ ++ public synchronized boolean nonceCountValid(long nonceCount) { ++ if ((count - offset) >= nonceCount || ++ (nonceCount > count - offset + seen.length)) { ++ return false; ++ } ++ int checkIndex = (int) ((nonceCount + offset) % seen.length); ++ if (seen[checkIndex]) { ++ return false; ++ } else { ++ seen[checkIndex] = true; ++ seen[count % seen.length] = false; ++ count++; ++ return true; ++ } + } + + public long getTimestamp() { +--- a/webapps/docs/config/valve.xml ++++ b/webapps/docs/config/valve.xml +@@ -805,12 +805,6 @@ + org.apache.catalina.authenticator.DigestAuthenticator.

+
+ +- +-

To protect against replay attacks, the DIGEST authenticator tracks +- client nonce and nonce count values. This attribute controls the size +- of that cache. If not specified, the default value of 1000 is used.

+-
+- + +

Controls the caching of pages that are protected by security + constraints. Setting this to false may help work around +@@ -828,6 +822,12 @@ + and/or across a cluster.

+
+ ++ ++

To protect against replay attacks, the DIGEST authenticator tracks ++ server nonce and nonce count values. This attribute controls the size ++ of that cache. If not specified, the default value of 1000 is used.

++
++ + +

The time, in milliseconds, that a server generated nonce will be + considered valid for use in authentication. If not specified, the +--- a/java/org/apache/tomcat/util/buf/B2CConverter.java ++++ b/java/org/apache/tomcat/util/buf/B2CConverter.java +@@ -52,6 +52,8 @@ + private static final Map encodingToCharsetCache = + new HashMap(); + ++ public static final Charset ISO_8859_1; ++ + static { + for (Charset charset: Charset.availableCharsets().values()) { + encodingToCharsetCache.put( +@@ -61,6 +63,14 @@ + alias.toLowerCase(Locale.US), charset); + } + } ++ Charset iso88591 = null; ++ try { ++ iso88591 = getCharset("ISO-8859-1"); ++ } catch (UnsupportedEncodingException e) { ++ // Impossible. All JVMs must support these. ++ e.printStackTrace(); ++ } ++ ISO_8859_1 = iso88591; + } + + public static Charset getCharset(String enc) +--- a/java/org/apache/catalina/util/MD5Encoder.java ++++ b/java/org/apache/catalina/util/MD5Encoder.java +@@ -50,7 +50,7 @@ + * @param binaryData Array containing the digest + * @return Encoded MD5, or null if encoding failed + */ +- public String encode( byte[] binaryData ) { ++ public static String encode( byte[] binaryData ) { + + if (binaryData.length != 16) + return null; diff -Nru tomcat7-7.0.21/debian/patches/CVE-2012-3546.patch tomcat7-7.0.21/debian/patches/CVE-2012-3546.patch --- tomcat7-7.0.21/debian/patches/CVE-2012-3546.patch 1970-01-01 07:00:00.000000000 +0700 +++ tomcat7-7.0.21/debian/patches/CVE-2012-3546.patch 2013-02-08 12:27:31.000000000 +0700 @@ -0,0 +1,47 @@ +Description: Remove unneeded handling of FORM authentication in RealmBase. +Origin: upstream +Author: ckuerste@gmx.ch +Bug: http://tomcat.apache.org/security-7.html#Fixed_in_Apache_Tomcat_7.0.30 +Ubuntu-Bug: https://bugs.launchpad.net/ubuntu/+source/tomcat7/+bug/1115053 +--- a/java/org/apache/catalina/realm/RealmBase.java ++++ b/java/org/apache/catalina/realm/RealmBase.java +@@ -45,7 +45,6 @@ + import org.apache.catalina.connector.Request; + import org.apache.catalina.connector.Response; + import org.apache.catalina.core.ApplicationSessionCookieConfig; +-import org.apache.catalina.deploy.LoginConfig; + import org.apache.catalina.deploy.SecurityCollection; + import org.apache.catalina.deploy.SecurityConstraint; + import org.apache.catalina.mbeans.MBeanUtils; +@@ -786,31 +785,6 @@ + if (constraints == null || constraints.length == 0) + return (true); + +- // Specifically allow access to the form login and form error pages +- // and the "j_security_check" action +- LoginConfig config = context.getLoginConfig(); +- if ((config != null) && +- (Constants.FORM_METHOD.equals(config.getAuthMethod()))) { +- String requestURI = request.getRequestPathMB().toString(); +- String loginPage = config.getLoginPage(); +- if (loginPage.equals(requestURI)) { +- if (log.isDebugEnabled()) +- log.debug(" Allow access to login page " + loginPage); +- return (true); +- } +- String errorPage = config.getErrorPage(); +- if (errorPage.equals(requestURI)) { +- if (log.isDebugEnabled()) +- log.debug(" Allow access to error page " + errorPage); +- return (true); +- } +- if (requestURI.endsWith(Constants.FORM_ACTION)) { +- if (log.isDebugEnabled()) +- log.debug(" Allow access to username/password submission"); +- return (true); +- } +- } +- + // Which user principal have we already authenticated? + Principal principal = request.getPrincipal(); + boolean status = false; diff -Nru tomcat7-7.0.21/debian/patches/CVE-2012-4431.patch tomcat7-7.0.21/debian/patches/CVE-2012-4431.patch --- tomcat7-7.0.21/debian/patches/CVE-2012-4431.patch 1970-01-01 07:00:00.000000000 +0700 +++ tomcat7-7.0.21/debian/patches/CVE-2012-4431.patch 2013-02-08 14:57:48.000000000 +0700 @@ -0,0 +1,51 @@ +Description: Improve session management in CsrfPreventionFilter +Origin: upstream +Author: ckuerste@gmx.ch +Bug: http://tomcat.apache.org/security-7.html#Fixed_in_Apache_Tomcat_7.0.32 +Ubuntu-Bug: https://bugs.launchpad.net/ubuntu/+source/tomcat7/+bug/1115053 +--- a/java/org/apache/catalina/filters/CsrfPreventionFilter.java ++++ b/java/org/apache/catalina/filters/CsrfPreventionFilter.java +@@ -34,6 +34,7 @@ + import javax.servlet.http.HttpServletRequest; + import javax.servlet.http.HttpServletResponse; + import javax.servlet.http.HttpServletResponseWrapper; ++import javax.servlet.http.HttpSession; + + import org.apache.juli.logging.Log; + import org.apache.juli.logging.LogFactory; +@@ -154,16 +155,19 @@ + } + } + ++ HttpSession session = req.getSession(false); ++ + @SuppressWarnings("unchecked") +- LruCache nonceCache = +- (LruCache) req.getSession(true).getAttribute( +- Constants.CSRF_NONCE_SESSION_ATTR_NAME); ++ LruCache nonceCache = (session == null) ? null ++ : (LruCache) session.getAttribute( ++ Constants.CSRF_NONCE_SESSION_ATTR_NAME); + + if (!skipNonceCheck) { + String previousNonce = + req.getParameter(Constants.CSRF_NONCE_REQUEST_PARAM); + +- if (nonceCache != null && !nonceCache.contains(previousNonce)) { ++ if (nonceCache == null || previousNonce == null || ++ !nonceCache.contains(previousNonce)) { + res.sendError(HttpServletResponse.SC_FORBIDDEN); + return; + } +@@ -171,7 +175,10 @@ + + if (nonceCache == null) { + nonceCache = new LruCache(nonceCacheSize); +- req.getSession().setAttribute( ++ if (session == null) { ++ session = req.getSession(true); ++ } ++ session.setAttribute( + Constants.CSRF_NONCE_SESSION_ATTR_NAME, nonceCache); + } + diff -Nru tomcat7-7.0.21/debian/patches/CVE-2012-4534.patch tomcat7-7.0.21/debian/patches/CVE-2012-4534.patch --- tomcat7-7.0.21/debian/patches/CVE-2012-4534.patch 1970-01-01 07:00:00.000000000 +0700 +++ tomcat7-7.0.21/debian/patches/CVE-2012-4534.patch 2013-02-08 12:27:31.000000000 +0700 @@ -0,0 +1,61 @@ +Description: Fix for CVE-2012-4534 Denial of Service Vulnerability +Origin: upstream +Author: ckuerste@gmx.ch +Bug: http://tomcat.apache.org/security-7.html#Fixed_in_Apache_Tomcat_7.0.28 +Ubuntu-Bug: https://bugs.launchpad.net/ubuntu/+source/tomcat7/+bug/1115053 +--- a/java/org/apache/tomcat/util/net/NioEndpoint.java ++++ b/java/org/apache/tomcat/util/net/NioEndpoint.java +@@ -1215,9 +1215,13 @@ + + public boolean processSendfile(SelectionKey sk, KeyAttachment attachment, boolean reg, boolean event) { + NioChannel sc = null; ++ if (log.isTraceEnabled()) { ++ log.trace("["+new java.sql.Date(System.currentTimeMillis()).toGMTString()+"] Processing send file. ["+sk+"] "); ++ } + try { + //unreg(sk,attachment);//only do this if we do process send file on a separate thread + SendfileData sd = attachment.getSendfileData(); ++ // setup the file channel + if ( sd.fchannel == null ) { + File f = new File(sd.fileName); + if ( !f.exists() ) { +@@ -1226,10 +1230,14 @@ + } + sd.fchannel = new FileInputStream(f).getChannel(); + } ++ ++ // configure output channel + sc = attachment.getChannel(); + sc.setSendFile(true); ++ // ssl channel is slightly different + WritableByteChannel wc = ((sc instanceof SecureNioChannel)?sc:sc.getIOChannel()); + ++ // we still have data in the buffer + if (sc.getOutboundRemaining()>0) { + if (sc.flushOutbound()) { + attachment.access(); +@@ -1256,7 +1264,6 @@ + attachment.setSendfileData(null); + try {sd.fchannel.close();}catch(Exception ignore){} + if ( sd.keepAlive ) { +- if (reg) { + if (log.isDebugEnabled()) { + log.debug("Connection is keep alive, registering back for OP_READ"); + } +@@ -1265,7 +1272,6 @@ + } else { + reg(sk,attachment,SelectionKey.OP_READ); + } +- } + } else { + if (log.isDebugEnabled()) { + log.debug("Send file connection is being closed"); +@@ -1273,7 +1279,7 @@ + cancelledKey(sk,SocketStatus.STOP,false); + return false; + } +- } else if ( attachment.interestOps() == 0 && reg ) { ++ } else { //if ( attachment.interestOps() == 0 && reg ) { + if (log.isDebugEnabled()) { + log.debug("OP_WRITE for sendilfe:"+sd.fileName); + } diff -Nru tomcat7-7.0.21/debian/patches/series tomcat7-7.0.21/debian/patches/series --- tomcat7-7.0.21/debian/patches/series 2011-09-07 15:47:47.000000000 +0700 +++ tomcat7-7.0.21/debian/patches/series 2013-02-08 15:04:57.000000000 +0700 @@ -9,3 +9,11 @@ 0009-Use-java.security.policy-file-in-catalina.sh.patch 0010-debianize-build-xml.patch 0011-fix-classpath-lintian-warnings.patch +CVE-2012-0022.patch +CVE-2011-3375.patch +CVE-2011-3376.patch +CVE-2012-2733.patch +CVE-2012-3439.patch +CVE-2012-3546.patch +CVE-2012-4431.patch +CVE-2012-4534.patch