From 7550bcd89f5a9f0a94b4e3ad93da02f3d98529f1 Mon Sep 17 00:00:00 2001 From: Michael Stack Date: Thu, 1 Dec 2016 16:34:04 -0800 Subject: Change CodedInputStream#DEFAULT_SIZE_LIMIT from 64MB to Integer.MAX_SIZE (0x7FFFFFF) #2228 M java/core/src/main/java/com/google/protobuf/CodedInputStream.java Set DEFAULT_SIZE_LIMIT to Integer.MAX_SIZE (Was 64MB). This is how it was in pre-2.7.0 pb. Changed size check to an overflow-conscious test (as it is later in tryRefillBuffer (making sizeLimit a long was to disruptive). M java/core/src/test/java/com/google/protobuf/CodedInputStreamTest.java Add two tests that echo tests recently added over in c++ to test parse of message sizes that are approach and are beyond the size limit. --- .../java/com/google/protobuf/CodedInputStream.java | 7 +- .../com/google/protobuf/CodedInputStreamTest.java | 78 +++++++++++++++++++++- 2 files changed, 80 insertions(+), 5 deletions(-) (limited to 'java') diff --git a/java/core/src/main/java/com/google/protobuf/CodedInputStream.java b/java/core/src/main/java/com/google/protobuf/CodedInputStream.java index e461fa28..14169dc4 100644 --- a/java/core/src/main/java/com/google/protobuf/CodedInputStream.java +++ b/java/core/src/main/java/com/google/protobuf/CodedInputStream.java @@ -60,7 +60,8 @@ import java.util.List; public abstract class CodedInputStream { private static final int DEFAULT_BUFFER_SIZE = 4096; private static final int DEFAULT_RECURSION_LIMIT = 100; - private static final int DEFAULT_SIZE_LIMIT = 64 << 20; // 64MB + // Integer.MAX_VALUE == 0x7FFFFFF == INT_MAX from limits.h + private static final int DEFAULT_SIZE_LIMIT = Integer.MAX_VALUE; /** Visible for subclasses. See setRecursionLimit() */ int recursionDepth; @@ -2762,9 +2763,9 @@ public abstract class CodedInputStream { throw InvalidProtocolBufferException.negativeSize(); } - // Verify that the message size so far has not exceeded sizeLimit. + // Integer-overflow-conscious check that the message size so far has not exceeded sizeLimit. int currentMessageSize = totalBytesRetired + pos + size; - if (currentMessageSize > sizeLimit) { + if (currentMessageSize - sizeLimit > 0) { throw InvalidProtocolBufferException.sizeLimitExceeded(); } diff --git a/java/core/src/test/java/com/google/protobuf/CodedInputStreamTest.java b/java/core/src/test/java/com/google/protobuf/CodedInputStreamTest.java index e2ab0df9..e440c7db 100644 --- a/java/core/src/test/java/com/google/protobuf/CodedInputStreamTest.java +++ b/java/core/src/test/java/com/google/protobuf/CodedInputStreamTest.java @@ -30,8 +30,6 @@ package com.google.protobuf; -import static org.junit.Assert.assertArrayEquals; - import protobuf_unittest.UnittestProto.BoolMessage; import protobuf_unittest.UnittestProto.Int32Message; import protobuf_unittest.UnittestProto.Int64Message; @@ -445,6 +443,82 @@ public class CodedInputStreamTest extends TestCase { } } + /** + * Test we can do messages that are up to CodedInputStream#DEFAULT_SIZE_LIMIT + * in size (2G or Integer#MAX_SIZE). + * @throws IOException + */ + public void testParseMessagesCloseTo2G() throws IOException { + byte[] serializedMessage = getBigSerializedMessage(); + // How many of these big messages do we need to take us near our 2G limit? + int count = Integer.MAX_VALUE / serializedMessage.length; + // Now make an inputstream that will fake a near 2G message of messages + // returning our big serialized message 'count' times. + InputStream is = new RepeatingInputStream(serializedMessage, count); + // Parse should succeed! + TestAllTypes.parseFrom(is); + } + + /** + * Test there is an exception if a message exceeds + * CodedInputStream#DEFAULT_SIZE_LIMIT in size (2G or Integer#MAX_SIZE). + * @throws IOException + */ + public void testParseMessagesOver2G() throws IOException { + byte[] serializedMessage = getBigSerializedMessage(); + // How many of these big messages do we need to take us near our 2G limit? + int count = Integer.MAX_VALUE / serializedMessage.length; + // Now add one to take us over the limit + count++; + // Now make an inputstream that will fake a near 2G message of messages + // returning our big serialized message 'count' times. + InputStream is = new RepeatingInputStream(serializedMessage, count); + try { + TestAllTypes.parseFrom(is); + fail("Should have thrown an exception!"); + } catch (InvalidProtocolBufferException e) { + assertTrue(e.getMessage().contains("too large")); + } + } + + /* + * @return A serialized big message. + */ + private static byte[] getBigSerializedMessage() { + byte[] value = new byte[16 * 1024 * 1024]; + ByteString bsValue = ByteString.wrap(value); + return TestAllTypes.newBuilder().setOptionalBytes(bsValue).build().toByteArray(); + } + + /* + * An input stream that repeats a byte arrays' content a number of times. + * Simulates really large input without consuming loads of memory. Used above + * to test the parsing behavior when the input size exceeds 2G or close to it. + */ + private static class RepeatingInputStream extends InputStream { + private final byte[] serializedMessage; + private final int count; + private int index = 0; + private int offset = 0; + + RepeatingInputStream(byte[] serializedMessage, int count) { + this.serializedMessage = serializedMessage; + this.count = count; + } + + @Override + public int read() throws IOException { + if (this.offset == this.serializedMessage.length) { + this.index++; + this.offset = 0; + } + if (this.index == this.count) { + return -1; + } + return this.serializedMessage[offset++]; + } + } + private TestRecursiveMessage makeRecursiveMessage(int depth) { if (depth == 0) { return TestRecursiveMessage.newBuilder().setI(5).build(); -- cgit v1.2.3