Medium severity5.9NVD Advisory· Published Oct 3, 2016· Updated May 6, 2026
CVE-2016-7046
CVE-2016-7046
Description
Red Hat JBoss Enterprise Application Platform (EAP) 7, when operating as a reverse-proxy with default buffer sizes, allows remote attackers to cause a denial of service (CPU and disk consumption) via a long URL.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
io.undertow:undertow-coreMaven | >= 1.4.0, < 1.4.3.Final | 1.4.3.Final |
io.undertow:undertow-coreMaven | < 1.3.25.Final | 1.3.25.Final |
Affected products
1Patches
1c518b5a17840UNDERTOW-835 Long URL proxy request lead to java.nio.BufferOverflowException
4 files changed · +203 −40
core/src/main/java/io/undertow/client/http/HttpRequestConduit.java+80 −21 modified@@ -61,16 +61,17 @@ final class HttpRequestConduit extends AbstractStreamSinkConduit<StreamSinkCondu private final ClientRequest request; private static final int STATE_BODY = 0; // Message body, normal pass-through operation - private static final int STATE_START = 1; // No headers written yet - private static final int STATE_HDR_NAME = 2; // Header name indexed by charIndex - private static final int STATE_HDR_D = 3; // Header delimiter ':' - private static final int STATE_HDR_DS = 4; // Header delimiter ': ' - private static final int STATE_HDR_VAL = 5; // Header value - private static final int STATE_HDR_EOL_CR = 6; // Header line CR - private static final int STATE_HDR_EOL_LF = 7; // Header line LF - private static final int STATE_HDR_FINAL_CR = 8; // Final CR - private static final int STATE_HDR_FINAL_LF = 9; // Final LF - private static final int STATE_BUF_FLUSH = 10; // flush the buffer and go to writing body + private static final int STATE_URL = 1; //Writing the URL + private static final int STATE_START = 2; // No headers written yet + private static final int STATE_HDR_NAME = 3; // Header name indexed by charIndex + private static final int STATE_HDR_D = 4; // Header delimiter ':' + private static final int STATE_HDR_DS = 5; // Header delimiter ': ' + private static final int STATE_HDR_VAL = 6; // Header value + private static final int STATE_HDR_EOL_CR = 7; // Header line CR + private static final int STATE_HDR_EOL_LF = 8; // Header line LF + private static final int STATE_HDR_FINAL_CR = 9; // Final CR + private static final int STATE_HDR_FINAL_LF = 10; // Final LF + private static final int STATE_BUF_FLUSH = 11; // flush the buffer and go to writing body private static final int MASK_STATE = 0x0000000F; private static final int FLAG_SHUTDOWN = 0x00000010; @@ -126,18 +127,34 @@ private int processWrite(int state, final ByteBuffer userData) throws IOExceptio } case STATE_START: { log.trace("Starting request"); - // we assume that our buffer has enough space for the initial request line plus one more CR+LF - assert buffer.remaining() >= 50; - request.getMethod().appendTo(buffer); - buffer.put((byte) ' '); - string = request.getPath(); - length = string.length(); - for (charIndex = 0; charIndex < length; charIndex ++) { - buffer.put((byte) string.charAt(charIndex)); + int len = request.getMethod().length() + request.getPath().length() + request.getProtocol().length() + 4; + + // test that our buffer has enough space for the initial request line plus one more CR+LF + if(len <= buffer.remaining()) { + assert buffer.remaining() >= 50; + request.getMethod().appendTo(buffer); + buffer.put((byte) ' '); + string = request.getPath(); + length = string.length(); + for (charIndex = 0; charIndex < length; charIndex++) { + buffer.put((byte) string.charAt(charIndex)); + } + buffer.put((byte) ' '); + request.getProtocol().appendTo(buffer); + buffer.put((byte) '\r').put((byte) '\n'); + } else { + StringBuilder sb = new StringBuilder(len); + sb.append(request.getMethod().toString()); + sb.append(" "); + sb.append(request.getPath()); + sb.append(" "); + sb.append(request.getProtocol()); + sb.append("\r\n"); + string = sb.toString(); + charIndex = 0; + state = STATE_URL; + break; } - buffer.put((byte) ' '); - request.getProtocol().appendTo(buffer); - buffer.put((byte) '\r').put((byte) '\n'); HeaderMap headers = request.getRequestHeaders(); nameIterator = headers.getHeaderNames().iterator(); if (! nameIterator.hasNext()) { @@ -441,6 +458,48 @@ private int processWrite(int state, final ByteBuffer userData) throws IOExceptio pooledBuffer = null; return STATE_BODY; } + case STATE_URL: { + for(int i = charIndex; i < string.length(); ++i) { + if(!buffer.hasRemaining()) { + buffer.flip(); + do { + res = next.write(buffer); + if (res == 0) { + log.trace("Continuation"); + this.charIndex = i; + this.string = string; + this.state = STATE_URL; + return STATE_URL; + } + } while (buffer.hasRemaining()); + buffer.clear(); + } + buffer.put((byte) string.charAt(i)); + } + + HeaderMap headers = request.getRequestHeaders(); + nameIterator = headers.getHeaderNames().iterator(); + state = STATE_HDR_NAME; + if (! nameIterator.hasNext()) { + log.trace("No request headers"); + buffer.put((byte) '\r').put((byte) '\n'); + buffer.flip(); + while (buffer.hasRemaining()) { + res = next.write(buffer); + if (res == 0) { + log.trace("Continuation"); + return STATE_BUF_FLUSH; + } + } + pooledBuffer.close(); + pooledBuffer = null; + log.trace("Body"); + return STATE_BODY; + } + headerName = nameIterator.next(); + charIndex = 0; + break; + } default: { throw new IllegalStateException(); }
core/src/main/java/io/undertow/protocols/http2/HpackEncoder.java+42 −19 modified@@ -80,6 +80,10 @@ public boolean shouldUseHuffman(HttpString header) { private final Deque<TableEntry> evictionQueue = new ArrayDeque<>(); private final Map<HttpString, List<TableEntry>> dynamicTable = new HashMap<>(); + private byte[] overflowData; + private int overflowPos; + private int overflowLength; + static { Map<HttpString, TableEntry[]> map = new HashMap<>(); for (int i = 1; i < STATIC_TABLE.length; ++i) { @@ -125,6 +129,17 @@ public HpackEncoder(int maxTableSize) { * @param target */ public State encode(HeaderMap headers, ByteBuffer target) { + if(overflowData != null) { + for(int i = overflowPos; i < overflowLength; ++i) { + if(!target.hasRemaining()) { + overflowPos = i; + return State.OVERFLOW; + } + target.put(overflowData[i]); + } + overflowData = null; + } + long it = headersIterator; if (headersIterator == -1) { handleTableSizeChange(target); @@ -165,44 +180,53 @@ public State encode(HeaderMap headers, ByteBuffer target) { TableEntry tableEntry = findInTable(headerName, val); required += (1 + val.length()); + boolean overflowing = false; - if (target.remaining() < required) { - this.headersIterator = it; - return State.UNDERFLOW; + ByteBuffer current = target; + if (current.remaining() < required) { + overflowing = true; + current = ByteBuffer.wrap(overflowData = new byte[required]); + overflowPos = 0; } boolean canIndex = hpackHeaderFunction.shouldUseIndexing(headerName, val) && (headerName.length() + val.length() + 32) < maxTableSize; //only index if it will fit if (tableEntry == null && canIndex) { //add the entry to the dynamic table - target.put((byte) (1 << 6)); - writeHuffmanEncodableName(target, headerName); - writeHuffmanEncodableValue(target, headerName, val); + current.put((byte) (1 << 6)); + writeHuffmanEncodableName(current, headerName); + writeHuffmanEncodableValue(current, headerName, val); addToDynamicTable(headerName, val); } else if (tableEntry == null) { //literal never indexed - target.put((byte) (1 << 4)); - writeHuffmanEncodableName(target, headerName); - writeHuffmanEncodableValue(target, headerName, val); + current.put((byte) (1 << 4)); + writeHuffmanEncodableName(current, headerName); + writeHuffmanEncodableValue(current, headerName, val); } else { //so we know something is already in the table if (val.equals(tableEntry.value)) { //the whole thing is in the table - target.put((byte) (1 << 7)); - encodeInteger(target, tableEntry.getPosition(), 7); + current.put((byte) (1 << 7)); + encodeInteger(current, tableEntry.getPosition(), 7); } else { if (canIndex) { //add the entry to the dynamic table - target.put((byte) (1 << 6)); - encodeInteger(target, tableEntry.getPosition(), 6); - writeHuffmanEncodableValue(target, headerName, val); + current.put((byte) (1 << 6)); + encodeInteger(current, tableEntry.getPosition(), 6); + writeHuffmanEncodableValue(current, headerName, val); addToDynamicTable(headerName, val); } else { - target.put((byte) (1 << 4)); - encodeInteger(target, tableEntry.getPosition(), 4); - writeHuffmanEncodableValue(target, headerName, val); + current.put((byte) (1 << 4)); + encodeInteger(current, tableEntry.getPosition(), 4); + writeHuffmanEncodableValue(current, headerName, val); } } } + if(overflowing) { + it = headers.fiNext(it); + this.headersIterator = it; + this.overflowLength = current.position(); + return State.OVERFLOW; + } } } @@ -346,8 +370,7 @@ private void handleTableSizeChange(ByteBuffer target) { public enum State { COMPLETE, - UNDERFLOW, - + OVERFLOW, } static class TableEntry {
core/src/main/java/io/undertow/server/handlers/proxy/ProxyHandler.java+3 −0 modified@@ -694,6 +694,9 @@ public void handleEvent(StreamSinkChannel channel) { } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); IoUtils.safeClose(channel); + } catch (Exception e) { + UndertowLogger.REQUEST_IO_LOGGER.ioException(new IOException(e)); + IoUtils.safeClose(channel); } }
core/src/test/java/io/undertow/server/handlers/LongURLTestCase.java+78 −0 added@@ -0,0 +1,78 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2014 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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. + */ + +package io.undertow.server.handlers; + +import java.io.IOException; + +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; +import io.undertow.testutils.AjpIgnore; +import io.undertow.testutils.DefaultServer; +import io.undertow.testutils.HttpClientUtils; +import io.undertow.testutils.TestHttpClient; + +/** + * @author Stuart Douglas + */ +@RunWith(DefaultServer.class) +@AjpIgnore(apacheOnly = true) +public class LongURLTestCase { + + private static final String MESSAGE = "HelloUrl"; + private static final int COUNT = 10000; + + @BeforeClass + public static void setup() { + final BlockingHandler blockingHandler = new BlockingHandler(); + DefaultServer.setRootHandler(blockingHandler); + blockingHandler.setRootHandler(new HttpHandler() { + @Override + public void handleRequest(final HttpServerExchange exchange) { + exchange.getResponseSender().send(exchange.getRelativePath()); + } + }); + } + + @Test + public void testLargeURL() throws IOException { + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < COUNT; ++i) { + sb.append(MESSAGE); + } + String message = sb.toString(); + + TestHttpClient client = new TestHttpClient(); + try { + HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/" + message); + HttpResponse result = client.execute(get); + Assert.assertEquals("/" + message, HttpClientUtils.readResponse(result)); + } finally { + client.getConnectionManager().shutdown(); + } + } + + +}
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
15- www.securityfocus.com/bid/93173nvdThird Party AdvisoryVDB Entry
- github.com/advisories/GHSA-3f57-w2rp-72fcghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2016-7046ghsaADVISORY
- bugzilla.redhat.com/show_bug.cginvdIssue TrackingWEB
- github.com/undertow-io/undertow/commit/c518b5a1784061d807efedcef0a03fcd35a53de2ghsaWEB
- issues.redhat.com/browse/UNDERTOW-835ghsaWEB
- security-tracker.debian.org/tracker/CVE-2016-7046ghsaWEB
- rhn.redhat.com/errata/RHSA-2016-2640.htmlnvd
- rhn.redhat.com/errata/RHSA-2016-2641.htmlnvd
- rhn.redhat.com/errata/RHSA-2016-2642.htmlnvd
- rhn.redhat.com/errata/RHSA-2016-2657.htmlnvd
- access.redhat.com/errata/RHSA-2017:3454nvd
- access.redhat.com/errata/RHSA-2017:3455nvd
- access.redhat.com/errata/RHSA-2017:3456nvd
- access.redhat.com/errata/RHSA-2017:3458nvd
News mentions
0No linked articles in our index yet.