From 039f71a04ba91bb336831abc07f87a422269accf Mon Sep 17 00:00:00 2001 From: Michael Mullins <221403458+alphacharlie-dev@users.noreply.github.com> Date: Fri, 31 Jul 2026 20:19:17 +0200 Subject: [PATCH] bound the request body size read by RPCServletUtils readContent copies the request InputStream into a ByteArrayOutputStream in a loop that only stops at end-of-stream, then materialises the result as a String, so peak memory is roughly twice the body size. No Content-Length ceiling and no running byte total are applied anywhere on the path, and the method is reached without authentication from both GWT-RPC (readContentAsGwtRpc) and RequestFactory (RequestFactoryServlet), so a single large POST can exhaust the heap. Servlet container max-post-size settings do not usually cover text/x-gwt-rpc or application/json. Reject up front when Content-Length already exceeds the limit, and keep a running total inside the loop so chunked transfers and an understated header are bounded as well. The limit defaults to 1 MiB and can be raised with the gwt.rpc.maxRequestBodyBytes system property; an unparseable value falls back to the default rather than disabling the check. Applications that send payloads larger than 1 MiB will need to set that property. --- .../gwt/user/server/rpc/RPCServletUtils.java | 53 ++++++++++++++++++- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/user/src/com/google/gwt/user/server/rpc/RPCServletUtils.java b/user/src/com/google/gwt/user/server/rpc/RPCServletUtils.java index ffcaad73ee..8a44634661 100644 --- a/user/src/com/google/gwt/user/server/rpc/RPCServletUtils.java +++ b/user/src/com/google/gwt/user/server/rpc/RPCServletUtils.java @@ -52,6 +52,40 @@ public class RPCServletUtils { */ static final int BUFFER_SIZE = 4096; + /** + * Name of the system property that overrides the maximum accepted request body size, in + * bytes. Values that are not a positive integer are ignored in favour of the default, so a + * malformed override cannot disable the cap. + */ + public static final String MAX_REQUEST_BODY_BYTES_PROPERTY = "gwt.rpc.maxRequestBodyBytes"; + + /** + * Default maximum request body size accepted by {@link #readContent}, in bytes. + */ + static final int DEFAULT_MAX_REQUEST_BODY_BYTES = 1024 * 1024; + + /** + * Maximum number of bytes {@link #readContent} buffers from a request body before rejecting + * it. Without a ceiling the read loop buffers an arbitrarily large attacker-supplied body + * into memory and then duplicates it as a String, exhausting the heap (CWE-400). + */ + static final int MAX_REQUEST_BODY_BYTES = readMaxRequestBodyBytes(); + + private static int readMaxRequestBodyBytes() { + String override = System.getProperty(MAX_REQUEST_BODY_BYTES_PROPERTY); + if (override != null) { + try { + int parsed = Integer.parseInt(override.trim()); + if (parsed > 0) { + return parsed; + } + } catch (NumberFormatException e) { + // Fall through to the default rather than leaving the body unbounded. + } + } + return DEFAULT_MAX_REQUEST_BODY_BYTES; + } + private static final String ACCEPT_ENCODING = "Accept-Encoding"; private static final String ATTACHMENT = "attachment"; @@ -201,7 +235,8 @@ public static boolean isExpectedException(Method serviceIntfMethod, * from or closed * @throws ServletException if the request's content type does not * equal the supplied expectedContentType or - * expectedCharSet + * expectedCharSet, or if the request body exceeds + * {@link #MAX_REQUEST_BODY_BYTES} */ public static String readContent(HttpServletRequest request, String expectedContentType, String expectedCharSet) @@ -215,17 +250,31 @@ public static String readContent(HttpServletRequest request, /* * Need to support 'Transfer-Encoding: chunked', so do not rely on - * presence of a 'Content-Length' request header. + * presence of a 'Content-Length' request header. When the header is present and already + * over the limit, reject before reading any of the body; the accumulated-byte check below + * is what enforces the limit for chunked requests and for an understated header. */ + int declaredLength = request.getContentLength(); + if (declaredLength > MAX_REQUEST_BODY_BYTES) { + throw new ServletException("Request body of " + declaredLength + + " bytes exceeds the maximum of " + MAX_REQUEST_BODY_BYTES + " bytes"); + } + InputStream in = request.getInputStream(); byte[] buffer = new byte[BUFFER_SIZE]; ByteArrayOutputStream out = new ByteArrayOutputStream(BUFFER_SIZE); try { + int totalRead = 0; while (true) { int byteCount = in.read(buffer); if (byteCount == -1) { break; } + totalRead += byteCount; + if (totalRead > MAX_REQUEST_BODY_BYTES) { + throw new ServletException("Request body exceeds the maximum of " + + MAX_REQUEST_BODY_BYTES + " bytes"); + } out.write(buffer, 0, byteCount); } return new String(out.toByteArray(), getCharset(expectedCharSet));