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));