VYPR
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.

PackageAffected versionsPatched versions
io.undertow:undertow-coreMaven
>= 1.4.0, < 1.4.3.Final1.4.3.Final
io.undertow:undertow-coreMaven
< 1.3.25.Final1.3.25.Final

Affected products

1

Patches

1
c518b5a17840

UNDERTOW-835 Long URL proxy request lead to java.nio.BufferOverflowException

https://github.com/undertow-io/undertowStuart DouglasSep 14, 2016via ghsa
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

News mentions

0

No linked articles in our index yet.