Author: markt Date: Tue Jan 30 13:59:11 2018 New Revision: 1822644 URL: http://svn.apache.org/viewvc?rev=1822644&view=rev Log: Enable strict host/port validation for all connectors.
Modified: tomcat/trunk/java/org/apache/coyote/AbstractProcessor.java tomcat/trunk/java/org/apache/coyote/ajp/AjpProcessor.java tomcat/trunk/java/org/apache/coyote/http11/Http11Processor.java tomcat/trunk/java/org/apache/coyote/http11/LocalStrings.properties tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties tomcat/trunk/java/org/apache/coyote/http2/Stream.java tomcat/trunk/java/org/apache/tomcat/util/http/parser/Host.java tomcat/trunk/webapps/docs/changelog.xml Modified: tomcat/trunk/java/org/apache/coyote/AbstractProcessor.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/AbstractProcessor.java?rev=1822644&r1=1822643&r2=1822644&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/AbstractProcessor.java (original) +++ tomcat/trunk/java/org/apache/coyote/AbstractProcessor.java Tue Jan 30 13:59:11 2018 @@ -27,6 +27,8 @@ import javax.servlet.RequestDispatcher; import org.apache.tomcat.util.ExceptionUtils; import org.apache.tomcat.util.buf.ByteChunk; +import org.apache.tomcat.util.buf.MessageBytes; +import org.apache.tomcat.util.http.parser.Host; import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState; import org.apache.tomcat.util.net.DispatchType; import org.apache.tomcat.util.net.SSLSupport; @@ -42,6 +44,9 @@ public abstract class AbstractProcessor private static final StringManager sm = StringManager.getManager(AbstractProcessor.class); + // Used to avoid useless B2C conversion on the host name. + private char[] hostNameC = new char[0]; + protected final Adapter adapter; protected final AsyncStateMachine asyncStateMachine; private volatile long asyncTimeout = -1; @@ -244,6 +249,68 @@ public abstract class AbstractProcessor } + protected void parseHost(MessageBytes valueMB) { + if (valueMB == null || valueMB.isNull()) { + populateHost(); + return; + } + + ByteChunk valueBC = valueMB.getByteChunk(); + byte[] valueB = valueBC.getBytes(); + int valueL = valueBC.getLength(); + int valueS = valueBC.getStart(); + if (hostNameC.length < valueL) { + hostNameC = new char[valueL]; + } + + try { + // Validates the host name + int colonPos = Host.parse(valueMB); + + // Extract the port information first, if any + if (colonPos != -1) { + int port = 0; + for (int i = colonPos + 1; i < valueL; i++) { + char c = (char) valueB[i + valueS]; + if (c < '0' || c > '9') { + response.setStatus(400); + setErrorState(ErrorState.CLOSE_CLEAN, null); + return; + } + port = port * 10 + c - '0'; + } + request.setServerPort(port); + + // Only need to copy the host name up to the : + valueL = colonPos; + } + + // Extract the host name + for (int i = 0; i < valueL; i++) { + hostNameC[i] = (char) valueB[i + valueS]; + } + request.serverName().setChars(hostNameC, 0, valueL); + + } catch (IllegalArgumentException e) { + // IllegalArgumentException indicates that the host name is invalid + response.setStatus(400); + setErrorState(ErrorState.CLOSE_CLEAN, null); + } + } + + + /** + * Called when a host name is not present in the request (e.g. HTTP/1.0). + * It populates the server name and port with appropriate information. The + * source is expected to vary by protocol. + * <p> + * The default implementation is a NO-OP. + */ + protected void populateHost() { + // NO-OP + } + + @Override public final void action(ActionCode actionCode, Object param) { switch (actionCode) { Modified: tomcat/trunk/java/org/apache/coyote/ajp/AjpProcessor.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/ajp/AjpProcessor.java?rev=1822644&r1=1822643&r2=1822644&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/ajp/AjpProcessor.java (original) +++ tomcat/trunk/java/org/apache/coyote/ajp/AjpProcessor.java Tue Jan 30 13:59:11 2018 @@ -39,7 +39,6 @@ import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.tomcat.util.ExceptionUtils; import org.apache.tomcat.util.buf.ByteChunk; -import org.apache.tomcat.util.buf.HexUtils; import org.apache.tomcat.util.buf.MessageBytes; import org.apache.tomcat.util.http.MimeHeaders; import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState; @@ -175,12 +174,6 @@ public class AjpProcessor extends Abstra /** - * Host name (used to avoid useless B2C conversion on the host name). - */ - private char[] hostNameC = new char[0]; - - - /** * Temp message bytes used for processing. */ private final MessageBytes tmpMB = MessageBytes.newInstance(); @@ -866,69 +859,20 @@ public class AjpProcessor extends Abstra /** - * Parse host. + * {@inheritDoc} + * <p> + * This implementation populates the server name and port from the local + * name and port provided by the AJP message. */ - private void parseHost(MessageBytes valueMB) { - - if (valueMB == null || valueMB.isNull()) { - // No host information (HTTP/1.0) - // Ensure the local port field is populated and then use it. - request.action(ActionCode.REQ_LOCALPORT_ATTRIBUTE, request); - request.setServerPort(request.getLocalPort()); - try { - request.serverName().duplicate(request.localName()); - } catch (IOException e) { - response.setStatus(400); - setErrorState(ErrorState.CLOSE_CLEAN, e); - } - return; - } - - ByteChunk valueBC = valueMB.getByteChunk(); - byte[] valueB = valueBC.getBytes(); - int valueL = valueBC.getLength(); - int valueS = valueBC.getStart(); - int colonPos = -1; - if (hostNameC.length < valueL) { - hostNameC = new char[valueL]; - } - - boolean ipv6 = (valueB[valueS] == '['); - boolean bracketClosed = false; - for (int i = 0; i < valueL; i++) { - char b = (char) valueB[i + valueS]; - hostNameC[i] = b; - if (b == ']') { - bracketClosed = true; - } else if (b == ':') { - if (!ipv6 || bracketClosed) { - colonPos = i; - break; - } - } - } - - if (colonPos < 0) { - request.serverName().setChars(hostNameC, 0, valueL); - } else { - - request.serverName().setChars(hostNameC, 0, colonPos); - - int port = 0; - int mult = 1; - for (int i = valueL - 1; i > colonPos; i--) { - int charValue = HexUtils.getDec(valueB[i + valueS]); - if (charValue == -1) { - // Invalid character - // 400 - Bad request - response.setStatus(400); - setErrorState(ErrorState.CLOSE_CLEAN, null); - break; - } - port = port + (charValue * mult); - mult = 10 * mult; - } - request.setServerPort(port); + @Override + protected void populateHost() { + // No host information (HTTP/1.0) + request.setServerPort(request.getLocalPort()); + try { + request.serverName().duplicate(request.localName()); + } catch (IOException e) { + response.setStatus(400); + setErrorState(ErrorState.CLOSE_CLEAN, e); } } Modified: tomcat/trunk/java/org/apache/coyote/http11/Http11Processor.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http11/Http11Processor.java?rev=1822644&r1=1822643&r2=1822644&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http11/Http11Processor.java (original) +++ tomcat/trunk/java/org/apache/coyote/http11/Http11Processor.java Tue Jan 30 13:59:11 2018 @@ -48,11 +48,9 @@ import org.apache.juli.logging.LogFactor import org.apache.tomcat.util.ExceptionUtils; import org.apache.tomcat.util.buf.Ascii; import org.apache.tomcat.util.buf.ByteChunk; -import org.apache.tomcat.util.buf.HexUtils; import org.apache.tomcat.util.buf.MessageBytes; import org.apache.tomcat.util.http.FastHttpDateFormat; import org.apache.tomcat.util.http.MimeHeaders; -import org.apache.tomcat.util.http.parser.Host; import org.apache.tomcat.util.log.UserDataHelper; import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState; import org.apache.tomcat.util.net.SSLSupport; @@ -133,12 +131,6 @@ public class Http11Processor extends Abs /** - * Host name (used to avoid useless B2C conversion on the host name). - */ - private char[] hostNameC = new char[0]; - - - /** * Instance of the new protocol to use after the HTTP connection has been * upgraded. */ @@ -973,94 +965,22 @@ public class Http11Processor extends Abs } } + /** - * Parse host. + * {@inheritDoc} + * <p> + * This implementation provides the server name from the default host and + * the server port from the local port. */ - private void parseHost(MessageBytes valueMB) { - - if (valueMB == null || valueMB.isNull()) { - // No host information (HTTP/1.0) - // Ensure the local port field is populated and then use it. - request.action(ActionCode.REQ_LOCALPORT_ATTRIBUTE, request); - request.setServerPort(request.getLocalPort()); - - // request.serverName() will be set to the default host name by the - // mapper - return; - } - - ByteChunk valueBC = valueMB.getByteChunk(); - byte[] valueB = valueBC.getBytes(); - int valueL = valueBC.getLength(); - int valueS = valueBC.getStart(); - int colonPos = -1; - if (hostNameC.length < valueL) { - hostNameC = new char[valueL]; - } - - // TODO - // To minimise breakage to existing systems, just report any errors. In - // a future release this will switch to returning a 400 response. - // Depending on user feedback, the 400 response may be made optional. - try { - Host.parse(valueMB); - } catch (IOException | IllegalArgumentException e) { - // IOException should never happen - // IllegalArgumentException indicates that the host name is invalid - UserDataHelper.Mode logMode = userDataHelper.getNextMode(); - if (logMode != null) { - String message = sm.getString("http11processor.host.parse", - valueMB.toString(), e.getMessage()); - switch (logMode) { - case INFO_THEN_DEBUG: - message += sm.getString("http11processor.fallToDebug"); - //$FALL-THROUGH$ - case INFO: - log.info(message, e); - break; - case DEBUG: - log.debug(message, e); - } - } - } - - boolean ipv6 = (valueB[valueS] == '['); - boolean bracketClosed = false; - for (int i = 0; i < valueL; i++) { - char b = (char) valueB[i + valueS]; - hostNameC[i] = b; - if (b == ']') { - bracketClosed = true; - } else if (b == ':') { - if (!ipv6 || bracketClosed) { - colonPos = i; - break; - } - } - } - - if (colonPos < 0) { - request.serverName().setChars(hostNameC, 0, valueL); - } else { - request.serverName().setChars(hostNameC, 0, colonPos); - - int port = 0; - int mult = 1; - for (int i = valueL - 1; i > colonPos; i--) { - int charValue = HexUtils.getDec(valueB[i + valueS]); - if (charValue == -1 || charValue > 9) { - // Invalid character - // 400 - Bad request - response.setStatus(400); - setErrorState(ErrorState.CLOSE_CLEAN, null); - break; - } - port = port + (charValue * mult); - mult = 10 * mult; - } - request.setServerPort(port); - } + @Override + protected void populateHost() { + // No host information (HTTP/1.0) + // Ensure the local port field is populated before using it. + request.action(ActionCode.REQ_LOCALPORT_ATTRIBUTE, request); + request.setServerPort(request.getLocalPort()); + // request.serverName() will be set to the default host name by the + // mapper } Modified: tomcat/trunk/java/org/apache/coyote/http11/LocalStrings.properties URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http11/LocalStrings.properties?rev=1822644&r1=1822643&r2=1822644&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http11/LocalStrings.properties (original) +++ tomcat/trunk/java/org/apache/coyote/http11/LocalStrings.properties Tue Jan 30 13:59:11 2018 @@ -19,7 +19,6 @@ abstractHttp11Protocol.httpUpgradeConfig http11processor.fallToDebug=\n Note: further occurrences of HTTP request parsing errors will be logged at DEBUG level. http11processor.header.parse=Error parsing HTTP request header -http11processor.host.parse=The host header [{0}] failed validation with the error [{1}]. Processing of the request will continue but Tomcat will reject these requests with a 400 response in a future release. http11processor.neverused=This method should never be used http11processor.request.inconsistentHosts=The host specified in the request line is not consistent with the host header http11processor.request.multipleHosts=The request contained multiple host headers Modified: tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties?rev=1822644&r1=1822643&r2=1822644&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties (original) +++ tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties Tue Jan 30 13:59:11 2018 @@ -79,6 +79,7 @@ stream.header.connection=Connection [{0} stream.header.contentLength=Connection [{0}], Stream [{1}], The content length header value [{2}] does not agree with the size of the data received [{3}] stream.header.debug=Connection [{0}], Stream [{1}], HTTP header [{2}], Value [{3}] stream.header.duplicate=Connection [{0}], Stream [{1}], received multiple [{3}] headers +stream.header.invalid=Connection [{0}], Stream [{1}], The header [{2}] contained invalid date [{3}] stream.header.noPath=Connection [{0}], Stream [{1}], The [:path] pseudo header was empty stream.header.required=Connection [{0}], Stream [{1}], One or more required headers was missing stream.header.te=Connection [{0}], Stream [{1}], HTTP header [te] is not permitted to have the value [{2}] in an HTTP/2 request Modified: tomcat/trunk/java/org/apache/coyote/http2/Stream.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Stream.java?rev=1822644&r1=1822643&r2=1822644&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http2/Stream.java (original) +++ tomcat/trunk/java/org/apache/coyote/http2/Stream.java Tue Jan 30 13:59:11 2018 @@ -41,6 +41,7 @@ import org.apache.juli.logging.LogFactor import org.apache.tomcat.util.buf.ByteChunk; import org.apache.tomcat.util.buf.MessageBytes; import org.apache.tomcat.util.http.MimeHeaders; +import org.apache.tomcat.util.http.parser.Host; import org.apache.tomcat.util.net.ApplicationBufferHandler; import org.apache.tomcat.util.res.StringManager; @@ -325,7 +326,14 @@ class Stream extends AbstractStream impl } case ":authority": { if (coyoteRequest.serverName().isNull()) { - int i = value.lastIndexOf(':'); + int i; + try { + i = Host.parse(value); + } catch (IllegalArgumentException iae) { + // Host value invalid + throw new HpackException(sm.getString("stream.header.invalid", + getConnectionId(), getIdentifier(), ":authority", value)); + } if (i > -1) { coyoteRequest.serverName().setString(value.substring(0, i)); coyoteRequest.setServerPort(Integer.parseInt(value.substring(i + 1))); Modified: tomcat/trunk/java/org/apache/tomcat/util/http/parser/Host.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/http/parser/Host.java?rev=1822644&r1=1822643&r2=1822644&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/util/http/parser/Host.java (original) +++ tomcat/trunk/java/org/apache/tomcat/util/http/parser/Host.java Tue Jan 30 13:59:11 2018 @@ -35,10 +35,8 @@ public class Host { * * @throws IllegalArgumentException If the host header value is not * specification compliant - * - * @throws IOException If a problem occurs reading the data from the input */ - public static int parse(MessageBytes mb) throws IOException { + public static int parse(MessageBytes mb) { return parse(new MessageBytesReader(mb)); } @@ -53,27 +51,30 @@ public class Host { * * @throws IllegalArgumentException If the host header value is not * specification compliant - * - * @throws IOException If a problem occurs reading the data from the input */ - public static int parse(String string) throws IOException { + public static int parse(String string) { return parse(new StringReader(string)); } - private static int parse(Reader reader) throws IOException { - reader.mark(1); - int first = reader.read(); - reader.reset(); - if (HttpParser.isAlpha(first)) { - return HttpParser.readHostDomainName(reader); - } else if (HttpParser.isNumeric(first)) { - return HttpParser.readHostIPv4(reader, false); - } else if ('[' == first) { - return HttpParser.readHostIPv6(reader); - } else { - // Invalid - throw new IllegalArgumentException(); + private static int parse(Reader reader) { + try { + reader.mark(1); + int first = reader.read(); + reader.reset(); + if (HttpParser.isAlpha(first)) { + return HttpParser.readHostDomainName(reader); + } else if (HttpParser.isNumeric(first)) { + return HttpParser.readHostIPv4(reader, false); + } else if ('[' == first) { + return HttpParser.readHostIPv6(reader); + } else { + // Invalid + throw new IllegalArgumentException(); + } + } catch (IOException ioe) { + // Should never happen + throw new IllegalArgumentException(ioe); } } Modified: tomcat/trunk/webapps/docs/changelog.xml URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=1822644&r1=1822643&r2=1822644&view=diff ============================================================================== --- tomcat/trunk/webapps/docs/changelog.xml (original) +++ tomcat/trunk/webapps/docs/changelog.xml Tue Jan 30 13:59:11 2018 @@ -125,6 +125,11 @@ local port rather than the connector configuration since the configured value maybe zero. (markt) </fix> + <add> + Enable strict validation of the provided host name and port for all + connectors. Requests with invalid host names and/or ports will be + rejected with a 400 response. (markt) + </add> </changelog> </subsection> <subsection name="Jasper"> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org