Allocate correct-sized array when parsing packed fixed-width primitives

But check that the given byte size is within the bounds of the byte[] first, otherwise we might get OutOfMemoryErrors on bitflipped input that tries to allocate huge arrays.

Add some tests that would have otherwise OutOfMemoryError'd in order to have some confidence that we won't OOM.

PiperOrigin-RevId: 673597245
pull/18092/head
Mark Hansen 5 months ago committed by Copybara-Service
parent ae51a89bf3
commit 4e8469cb45
  1. 28
      java/core/src/main/java/com/google/protobuf/ArrayDecoders.java
  2. 26
      java/core/src/main/java/com/google/protobuf/BooleanArrayList.java
  3. 26
      java/core/src/main/java/com/google/protobuf/DoubleArrayList.java
  4. 26
      java/core/src/main/java/com/google/protobuf/FloatArrayList.java
  5. 26
      java/core/src/main/java/com/google/protobuf/IntArrayList.java
  6. 26
      java/core/src/main/java/com/google/protobuf/LongArrayList.java
  7. 36
      java/core/src/test/java/com/google/protobuf/ArrayDecodersTest.java

@ -459,7 +459,12 @@ final class ArrayDecoders {
throws InvalidProtocolBufferException {
final IntArrayList output = (IntArrayList) list;
position = decodeVarint32(data, position, registers);
final int fieldLimit = position + registers.int1;
final int packedDataByteSize = registers.int1;
final int fieldLimit = position + packedDataByteSize;
if (fieldLimit > data.length) {
throw InvalidProtocolBufferException.truncatedMessage();
}
output.ensureCapacity(output.size() + packedDataByteSize / 4);
while (position < fieldLimit) {
output.addInt(decodeFixed32(data, position));
position += 4;
@ -476,7 +481,12 @@ final class ArrayDecoders {
throws InvalidProtocolBufferException {
final LongArrayList output = (LongArrayList) list;
position = decodeVarint32(data, position, registers);
final int fieldLimit = position + registers.int1;
final int packedDataByteSize = registers.int1;
final int fieldLimit = position + packedDataByteSize;
if (fieldLimit > data.length) {
throw InvalidProtocolBufferException.truncatedMessage();
}
output.ensureCapacity(output.size() + packedDataByteSize / 8);
while (position < fieldLimit) {
output.addLong(decodeFixed64(data, position));
position += 8;
@ -493,7 +503,12 @@ final class ArrayDecoders {
throws InvalidProtocolBufferException {
final FloatArrayList output = (FloatArrayList) list;
position = decodeVarint32(data, position, registers);
final int fieldLimit = position + registers.int1;
final int packedDataByteSize = registers.int1;
final int fieldLimit = position + packedDataByteSize;
if (fieldLimit > data.length) {
throw InvalidProtocolBufferException.truncatedMessage();
}
output.ensureCapacity(output.size() + packedDataByteSize / 4);
while (position < fieldLimit) {
output.addFloat(decodeFloat(data, position));
position += 4;
@ -510,7 +525,12 @@ final class ArrayDecoders {
throws InvalidProtocolBufferException {
final DoubleArrayList output = (DoubleArrayList) list;
position = decodeVarint32(data, position, registers);
final int fieldLimit = position + registers.int1;
final int packedDataByteSize = registers.int1;
final int fieldLimit = position + packedDataByteSize;
if (fieldLimit > data.length) {
throw InvalidProtocolBufferException.truncatedMessage();
}
output.ensureCapacity(output.size() + packedDataByteSize / 8);
while (position < fieldLimit) {
output.addDouble(decodeDouble(data, position));
position += 8;

@ -170,8 +170,7 @@ final class BooleanArrayList extends AbstractProtobufList<Boolean>
public void addBoolean(boolean element) {
ensureIsMutable();
if (size == array.length) {
// Resize to 1.5x the size
int length = ((size * 3) / 2) + 1;
int length = growSize(array.length);
boolean[] newArray = new boolean[length];
System.arraycopy(array, 0, newArray, 0, size);
@ -192,8 +191,7 @@ final class BooleanArrayList extends AbstractProtobufList<Boolean>
// Shift everything over to make room
System.arraycopy(array, index, array, index + 1, size - index);
} else {
// Resize to 1.5x the size
int length = ((size * 3) / 2) + 1;
int length = growSize(array.length);
boolean[] newArray = new boolean[length];
// Copy the first part directly
@ -255,6 +253,26 @@ final class BooleanArrayList extends AbstractProtobufList<Boolean>
return value;
}
/** Ensures the backing array can fit at least minCapacity elements. */
void ensureCapacity(int minCapacity) {
if (minCapacity <= array.length) {
return;
}
// To avoid quadratic copying when calling .addAllFoo(List) in a loop, we must not size to
// exactly the requested capacity, but must exponentially grow instead. This is similar
// behaviour to ArrayList.
int n = array.length;
while (n < minCapacity) {
n = growSize(n);
}
array = Arrays.copyOf(array, n);
}
private static int growSize(int previousSize) {
// Resize to 1.5x the size
return ((previousSize * 3) / 2) + 1;
}
/**
* Ensures that the provided {@code index} is within the range of {@code [0, size]}. Throws an
* {@link IndexOutOfBoundsException} if it is not.

@ -170,8 +170,7 @@ final class DoubleArrayList extends AbstractProtobufList<Double>
public void addDouble(double element) {
ensureIsMutable();
if (size == array.length) {
// Resize to 1.5x the size
int length = ((size * 3) / 2) + 1;
int length = growSize(array.length);
double[] newArray = new double[length];
System.arraycopy(array, 0, newArray, 0, size);
@ -192,8 +191,7 @@ final class DoubleArrayList extends AbstractProtobufList<Double>
// Shift everything over to make room
System.arraycopy(array, index, array, index + 1, size - index);
} else {
// Resize to 1.5x the size
int length = ((size * 3) / 2) + 1;
int length = growSize(array.length);
double[] newArray = new double[length];
// Copy the first part directly
@ -255,6 +253,26 @@ final class DoubleArrayList extends AbstractProtobufList<Double>
return value;
}
/** Ensures the backing array can fit at least minCapacity elements. */
void ensureCapacity(int minCapacity) {
if (minCapacity <= array.length) {
return;
}
// To avoid quadratic copying when calling .addAllFoo(List) in a loop, we must not size to
// exactly the requested capacity, but must exponentially grow instead. This is similar
// behaviour to ArrayList.
int n = array.length;
while (n < minCapacity) {
n = growSize(n);
}
array = Arrays.copyOf(array, n);
}
private static int growSize(int previousSize) {
// Resize to 1.5x the size
return ((previousSize * 3) / 2) + 1;
}
/**
* Ensures that the provided {@code index} is within the range of {@code [0, size]}. Throws an
* {@link IndexOutOfBoundsException} if it is not.

@ -169,8 +169,7 @@ final class FloatArrayList extends AbstractProtobufList<Float>
public void addFloat(float element) {
ensureIsMutable();
if (size == array.length) {
// Resize to 1.5x the size
int length = ((size * 3) / 2) + 1;
int length = growSize(array.length);
float[] newArray = new float[length];
System.arraycopy(array, 0, newArray, 0, size);
@ -191,8 +190,7 @@ final class FloatArrayList extends AbstractProtobufList<Float>
// Shift everything over to make room
System.arraycopy(array, index, array, index + 1, size - index);
} else {
// Resize to 1.5x the size
int length = ((size * 3) / 2) + 1;
int length = growSize(array.length);
float[] newArray = new float[length];
// Copy the first part directly
@ -254,6 +252,26 @@ final class FloatArrayList extends AbstractProtobufList<Float>
return value;
}
/** Ensures the backing array can fit at least minCapacity elements. */
void ensureCapacity(int minCapacity) {
if (minCapacity <= array.length) {
return;
}
// To avoid quadratic copying when calling .addAllFoo(List) in a loop, we must not size to
// exactly the requested capacity, but must exponentially grow instead. This is similar
// behaviour to ArrayList.
int n = array.length;
while (n < minCapacity) {
n = growSize(n);
}
array = Arrays.copyOf(array, n);
}
private static int growSize(int previousSize) {
// Resize to 1.5x the size
return ((previousSize * 3) / 2) + 1;
}
/**
* Ensures that the provided {@code index} is within the range of {@code [0, size]}. Throws an
* {@link IndexOutOfBoundsException} if it is not.

@ -169,8 +169,7 @@ final class IntArrayList extends AbstractProtobufList<Integer>
public void addInt(int element) {
ensureIsMutable();
if (size == array.length) {
// Resize to 1.5x the size
int length = ((size * 3) / 2) + 1;
int length = growSize(array.length);
int[] newArray = new int[length];
System.arraycopy(array, 0, newArray, 0, size);
@ -191,8 +190,7 @@ final class IntArrayList extends AbstractProtobufList<Integer>
// Shift everything over to make room
System.arraycopy(array, index, array, index + 1, size - index);
} else {
// Resize to 1.5x the size
int length = ((size * 3) / 2) + 1;
int length = growSize(array.length);
int[] newArray = new int[length];
// Copy the first part directly
@ -254,6 +252,26 @@ final class IntArrayList extends AbstractProtobufList<Integer>
return value;
}
/** Ensures the backing array can fit at least minCapacity elements. */
void ensureCapacity(int minCapacity) {
if (minCapacity <= array.length) {
return;
}
// To avoid quadratic copying when calling .addAllFoo(List) in a loop, we must not size to
// exactly the requested capacity, but must exponentially grow instead. This is similar
// behaviour to ArrayList.
int n = array.length;
while (n < minCapacity) {
n = growSize(n);
}
array = Arrays.copyOf(array, n);
}
private static int growSize(int previousSize) {
// Resize to 1.5x the size
return ((previousSize * 3) / 2) + 1;
}
/**
* Ensures that the provided {@code index} is within the range of {@code [0, size]}. Throws an
* {@link IndexOutOfBoundsException} if it is not.

@ -169,8 +169,7 @@ final class LongArrayList extends AbstractProtobufList<Long>
public void addLong(long element) {
ensureIsMutable();
if (size == array.length) {
// Resize to 1.5x the size
int length = ((size * 3) / 2) + 1;
int length = growSize(array.length);
long[] newArray = new long[length];
System.arraycopy(array, 0, newArray, 0, size);
@ -191,8 +190,7 @@ final class LongArrayList extends AbstractProtobufList<Long>
// Shift everything over to make room
System.arraycopy(array, index, array, index + 1, size - index);
} else {
// Resize to 1.5x the size
int length = ((size * 3) / 2) + 1;
int length = growSize(array.length);
long[] newArray = new long[length];
// Copy the first part directly
@ -254,6 +252,26 @@ final class LongArrayList extends AbstractProtobufList<Long>
return value;
}
/** Ensures the backing array can fit at least minCapacity elements. */
void ensureCapacity(int minCapacity) {
if (minCapacity <= array.length) {
return;
}
// To avoid quadratic copying when calling .addAllFoo(List) in a loop, we must not size to
// exactly the requested capacity, but must exponentially grow instead. This is similar
// behaviour to ArrayList.
int n = array.length;
while (n < minCapacity) {
n = growSize(n);
}
array = Arrays.copyOf(array, n);
}
private static int growSize(int previousSize) {
// Resize to 1.5x the size
return ((previousSize * 3) / 2) + 1;
}
/**
* Ensures that the provided {@code index} is within the range of {@code [0, size]}. Throws an
* {@link IndexOutOfBoundsException} if it is not.

@ -159,6 +159,42 @@ public class ArrayDecodersTest {
packedSizeBytesNoTag(-1), 0, new IntArrayList(), registers));
}
@Test
public void testDecodePackedFixed32List_2gb_beyondEndOfArray() {
assertThrows(
InvalidProtocolBufferException.class,
() ->
ArrayDecoders.decodePackedFixed32List(
packedSizeBytesNoTag(2_000_000_000), 0, new IntArrayList(), registers));
}
@Test
public void testDecodePackedFixed64List_2gb_beyondEndOfArray() {
assertThrows(
InvalidProtocolBufferException.class,
() ->
ArrayDecoders.decodePackedFixed64List(
packedSizeBytesNoTag(2_000_000_000), 0, new LongArrayList(), registers));
}
@Test
public void testDecodePackedFloatList_2gb_beyondEndOfArray() {
assertThrows(
InvalidProtocolBufferException.class,
() ->
ArrayDecoders.decodePackedFloatList(
packedSizeBytesNoTag(2_000_000_000), 0, new FloatArrayList(), registers));
}
@Test
public void testDecodePackedDoubleList_2gb_beyondEndOfArray() {
assertThrows(
InvalidProtocolBufferException.class,
() ->
ArrayDecoders.decodePackedDoubleList(
packedSizeBytesNoTag(2_000_000_000), 0, new DoubleArrayList(), registers));
}
@Test
public void testDecodePackedFixed64List_negativeSize() {
assertThrows(

Loading…
Cancel
Save