VYPR
Critical severityNVD Advisory· Published Jul 9, 2020· Updated Aug 5, 2024

CVE-2019-17638

CVE-2019-17638

Description

In Eclipse Jetty, versions 9.4.27.v20200227 to 9.4.29.v20200521, in case of too large response headers, Jetty throws an exception to produce an HTTP 431 error. When this happens, the ByteBuffer containing the HTTP response headers is released back to the ByteBufferPool twice. Because of this double release, two threads can acquire the same ByteBuffer from the pool and while thread1 is about to use the ByteBuffer to write response1 data, thread2 fills the ByteBuffer with other data. Thread1 then proceeds to write the buffer that now contains different data. This results in client1, which issued request1 seeing data from another request or response which could contain sensitive data belonging to client2 (HTTP session ids, authentication credentials, etc.). If the Jetty version cannot be upgraded, the vulnerability can be significantly reduced by configuring a responseHeaderSize significantly larger than the requestHeaderSize (12KB responseHeaderSize and 8KB requestHeaderSize).

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Eclipse Jetty 9.4.27-9.4.29 double-releases ByteBuffer on response header overflow, causing buffer corruption and potential cross-client data leakage.

Vulnerability

Description

In Eclipse Jetty versions 9.4.27.v20200227 through 9.4.29.v20200521, when a response header exceeds the configured maximum size, Jetty throws an exception to produce an HTTP 431 error. During this error handling, the ByteBuffer containing the HTTP response headers is released back to the ByteBufferPool twice. This double release allows two threads to acquire the same ByteBuffer from the pool concurrently [1][2].

Exploitation

Conditions

An attacker can trigger the vulnerability by sending a request that causes the server to generate response headers larger than the limit (e.g., via a crafted request that results in many or large headers). When the 431 error occurs, the buffer is freed twice. Under concurrent load, a second thread may allocate the same buffer and fill it with data from a different request or response. The first thread then writes the now-corrupted buffer, causing data intended for one client to be sent to another [2][4].

Impact

A client that issued a legitimate request may receive data from another client's request or response. This leaked data can include sensitive information such as HTTP session IDs, authentication credentials, or other confidential content [1][2]. The vulnerability has a CVSS score of 8.3 (High) and is associated with CWE-672 (Operation on a Resource after Expiration or Release) and CWE-675 (Duplicate Operations on Resource) [4].

Mitigation

The issue is fixed in Jetty versions 9.4.30.v20200611 and later [3]. If upgrading is not immediately possible, the risk can be significantly reduced by configuring the response header size to be substantially larger than the request header size (e.g., 12KB responseHeaderSize and 8KB requestHeaderSize) [1]. No workaround fully eliminates the vulnerability, but this configuration reduces the likelihood of triggering the double-release condition.

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.eclipse.jetty:jetty-serverMaven
>= 9.4.27, < 9.4.30.v202006119.4.30.v20200611

Affected products

2

Patches

1
ff8ae56fa939

Issue #4936 response buffer corruption (#4937)

https://github.com/eclipse/jetty.projectGreg WilkinsJun 3, 2020via ghsa
2 files changed · +170 23
  • jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java+28 23 modified
    @@ -72,7 +72,6 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
         private final HttpParser _parser;
         private final AtomicInteger _contentBufferReferences = new AtomicInteger();
         private volatile ByteBuffer _requestBuffer = null;
    -    private volatile ByteBuffer _chunk = null;
         private final BlockingReadCallback _blockingReadCallback = new BlockingReadCallback();
         private final AsyncReadCallback _asyncReadCallback = new AsyncReadCallback();
         private final SendCallback _sendCallback = new SendCallback();
    @@ -442,11 +441,6 @@ else if (_parser.inContentState() && _generator.isPersistent())
                     _parser.close();
             }
     
    -        // Not in a race here with onFillable, because it has given up control before calling handle.
    -        // in a slight race with #completed, but not sure what to do with that anyway.
    -        if (_chunk != null)
    -            _bufferPool.release(_chunk);
    -        _chunk = null;
             _generator.reset();
     
             // if we are not called from the onfillable thread, schedule completion
    @@ -693,6 +687,7 @@ private class SendCallback extends IteratingCallback
             private boolean _lastContent;
             private Callback _callback;
             private ByteBuffer _header;
    +        private ByteBuffer _chunk;
             private boolean _shutdownOut;
     
             private SendCallback()
    @@ -737,10 +732,9 @@ public Action process() throws Exception
                 if (_callback == null)
                     throw new IllegalStateException();
     
    -            ByteBuffer chunk = _chunk;
                 while (true)
                 {
    -                HttpGenerator.Result result = _generator.generateResponse(_info, _head, _header, chunk, _content, _lastContent);
    +                HttpGenerator.Result result = _generator.generateResponse(_info, _head, _header, _chunk, _content, _lastContent);
                     if (LOG.isDebugEnabled())
                         LOG.debug("generate: {} for {} ({},{},{})@{}",
                             result,
    @@ -763,31 +757,29 @@ public Action process() throws Exception
     
                         case HEADER_OVERFLOW:
                         {
    -                        int capacity = _header.capacity();
    -                        _bufferPool.release(_header);
    -                        if (capacity >= _config.getResponseHeaderSize())
    +                        if (_header.capacity() >= _config.getResponseHeaderSize())
                                 throw new BadMessageException(INTERNAL_SERVER_ERROR_500, "Response header too large");
    +                        releaseHeader();
                             _header = _bufferPool.acquire(_config.getResponseHeaderSize(), HEADER_BUFFER_DIRECT);
                             continue;
                         }
                         case NEED_CHUNK:
                         {
    -                        chunk = _chunk = _bufferPool.acquire(HttpGenerator.CHUNK_SIZE, CHUNK_BUFFER_DIRECT);
    +                        _chunk = _bufferPool.acquire(HttpGenerator.CHUNK_SIZE, CHUNK_BUFFER_DIRECT);
                             continue;
                         }
                         case NEED_CHUNK_TRAILER:
                         {
    -                        if (_chunk != null)
    -                            _bufferPool.release(_chunk);
    -                        chunk = _chunk = _bufferPool.acquire(_config.getResponseHeaderSize(), CHUNK_BUFFER_DIRECT);
    +                        releaseChunk();
    +                        _chunk = _bufferPool.acquire(_config.getResponseHeaderSize(), CHUNK_BUFFER_DIRECT);
                             continue;
                         }
                         case FLUSH:
                         {
                             // Don't write the chunk or the content if this is a HEAD response, or any other type of response that should have no content
                             if (_head || _generator.isNoContent())
                             {
    -                            BufferUtil.clear(chunk);
    +                            BufferUtil.clear(_chunk);
                                 BufferUtil.clear(_content);
                             }
     
    @@ -798,10 +790,10 @@ public Action process() throws Exception
                                 gatherWrite += 4;
                                 bytes += _header.remaining();
                             }
    -                        if (BufferUtil.hasContent(chunk))
    +                        if (BufferUtil.hasContent(_chunk))
                             {
                                 gatherWrite += 2;
    -                            bytes += chunk.remaining();
    +                            bytes += _chunk.remaining();
                             }
                             if (BufferUtil.hasContent(_content))
                             {
    @@ -812,10 +804,10 @@ public Action process() throws Exception
                             switch (gatherWrite)
                             {
                                 case 7:
    -                                getEndPoint().write(this, _header, chunk, _content);
    +                                getEndPoint().write(this, _header, _chunk, _content);
                                     break;
                                 case 6:
    -                                getEndPoint().write(this, _header, chunk);
    +                                getEndPoint().write(this, _header, _chunk);
                                     break;
                                 case 5:
                                     getEndPoint().write(this, _header, _content);
    @@ -824,10 +816,10 @@ public Action process() throws Exception
                                     getEndPoint().write(this, _header);
                                     break;
                                 case 3:
    -                                getEndPoint().write(this, chunk, _content);
    +                                getEndPoint().write(this, _chunk, _content);
                                     break;
                                 case 2:
    -                                getEndPoint().write(this, chunk);
    +                                getEndPoint().write(this, _chunk);
                                     break;
                                 case 1:
                                     getEndPoint().write(this, _content);
    @@ -871,10 +863,23 @@ private Callback release()
                 _callback = null;
                 _info = null;
                 _content = null;
    +            releaseHeader();
    +            releaseChunk();
    +            return complete;
    +        }
    +
    +        private void releaseHeader()
    +        {
                 if (_header != null)
                     _bufferPool.release(_header);
                 _header = null;
    -            return complete;
    +        }
    +
    +        private void releaseChunk()
    +        {
    +            if (_chunk != null)
    +                _bufferPool.release(_chunk);
    +            _chunk = null;
             }
     
             @Override
    
  • jetty-server/src/test/java/org/eclipse/jetty/server/LargeHeaderTest.java+142 0 added
    @@ -0,0 +1,142 @@
    +//
    +//  ========================================================================
    +//  Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
    +//  ------------------------------------------------------------------------
    +//  All rights reserved. This program and the accompanying materials
    +//  are made available under the terms of the Eclipse Public License v1.0
    +//  and Apache License v2.0 which accompanies this distribution.
    +//
    +//      The Eclipse Public License is available at
    +//      http://www.eclipse.org/legal/epl-v10.html
    +//
    +//      The Apache License v2.0 is available at
    +//      http://www.opensource.org/licenses/apache2.0.php
    +//
    +//  You may elect to redistribute this code under either of these licenses.
    +//  ========================================================================
    +//
    +
    +package org.eclipse.jetty.server;
    +
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.io.OutputStream;
    +import java.io.PrintWriter;
    +import java.net.Socket;
    +import java.util.Arrays;
    +import java.util.concurrent.ExecutorService;
    +import java.util.concurrent.Executors;
    +import java.util.concurrent.TimeUnit;
    +import javax.servlet.http.HttpServletRequest;
    +import javax.servlet.http.HttpServletResponse;
    +
    +import org.eclipse.jetty.http.HttpHeader;
    +import org.eclipse.jetty.http.HttpTester;
    +import org.eclipse.jetty.http.MimeTypes;
    +import org.eclipse.jetty.server.handler.AbstractHandler;
    +import org.eclipse.jetty.server.handler.ErrorHandler;
    +import org.eclipse.jetty.util.IO;
    +import org.eclipse.jetty.util.component.LifeCycle;
    +import org.eclipse.jetty.util.log.Log;
    +import org.eclipse.jetty.util.log.Logger;
    +import org.junit.jupiter.api.AfterEach;
    +import org.junit.jupiter.api.BeforeEach;
    +import org.junit.jupiter.api.Test;
    +
    +import static java.nio.charset.StandardCharsets.UTF_8;
    +import static org.hamcrest.MatcherAssert.assertThat;
    +import static org.hamcrest.Matchers.is;
    +
    +public class LargeHeaderTest
    +{
    +    private Server server;
    +
    +    @BeforeEach
    +    public void setup() throws Exception
    +    {
    +        server = new Server();
    +
    +        HttpConfiguration config = new HttpConfiguration();
    +        HttpConnectionFactory http = new HttpConnectionFactory(config);
    +
    +        ServerConnector connector = new ServerConnector(server, http);
    +        connector.setPort(0);
    +        connector.setIdleTimeout(5000);
    +        server.addConnector(connector);
    +
    +        server.setErrorHandler(new ErrorHandler());
    +
    +        server.setHandler(new AbstractHandler()
    +        {
    +            final String largeHeaderValue;
    +
    +            {
    +                byte[] bytes = new byte[8 * 1024];
    +                Arrays.fill(bytes, (byte)'X');
    +                largeHeaderValue = "LargeHeaderOver8k-" + new String(bytes, UTF_8) + "_Z_";
    +            }
    +
    +            @Override
    +            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
    +            {
    +                response.setHeader(HttpHeader.CONTENT_TYPE.toString(), MimeTypes.Type.TEXT_HTML.toString());
    +                response.setHeader("LongStr", largeHeaderValue);
    +                PrintWriter writer = response.getWriter();
    +                writer.write("<html><h1>FOO</h1></html>");
    +                writer.flush();
    +                response.flushBuffer();
    +                baseRequest.setHandled(true);
    +            }
    +        });
    +        server.start();
    +    }
    +
    +    @AfterEach
    +    public void teardown()
    +    {
    +        LifeCycle.stop(server);
    +    }
    +
    +    @Test
    +    public void testLargeHeader() throws Throwable
    +    {
    +        final Logger CLIENTLOG = Log.getLogger(LargeHeaderTest.class).getLogger(".client");
    +        ExecutorService executorService = Executors.newFixedThreadPool(8);
    +
    +        int localPort = server.getURI().getPort();
    +        String rawRequest = "GET / HTTP/1.1\r\n" +
    +            "Host: localhost:" + localPort + "\r\n" +
    +            "\r\n";
    +
    +        Throwable issues = new Throwable();
    +
    +        for (int i = 0; i < 500; ++i)
    +        {
    +            executorService.submit(() ->
    +            {
    +                try (Socket client = new Socket("localhost", localPort);
    +                     OutputStream output = client.getOutputStream();
    +                     InputStream input = client.getInputStream())
    +                {
    +                    output.write(rawRequest.getBytes(UTF_8));
    +                    output.flush();
    +
    +                    String rawResponse = IO.toString(input, UTF_8);
    +                    HttpTester.Response response = HttpTester.parseResponse(rawResponse);
    +                    assertThat(response.getStatus(), is(500));
    +                }
    +                catch (Throwable t)
    +                {
    +                    CLIENTLOG.warn("Client Issue", t);
    +                    issues.addSuppressed(t);
    +                }
    +            });
    +        }
    +
    +        executorService.awaitTermination(5, TimeUnit.SECONDS);
    +        if (issues.getSuppressed().length > 0)
    +        {
    +            throw issues;
    +        }
    +    }
    +}
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

35

News mentions

1