Skip to content

Commit 4e8469c

Browse files
mhansencopybara-github
authored andcommitted
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
1 parent ae51a89 commit 4e8469c

File tree

7 files changed

+170
-24
lines changed

7 files changed

+170
-24
lines changed

java/core/src/main/java/com/google/protobuf/ArrayDecoders.java

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,12 @@ static int decodePackedFixed32List(
459459
throws InvalidProtocolBufferException {
460460
final IntArrayList output = (IntArrayList) list;
461461
position = decodeVarint32(data, position, registers);
462-
final int fieldLimit = position + registers.int1;
462+
final int packedDataByteSize = registers.int1;
463+
final int fieldLimit = position + packedDataByteSize;
464+
if (fieldLimit > data.length) {
465+
throw InvalidProtocolBufferException.truncatedMessage();
466+
}
467+
output.ensureCapacity(output.size() + packedDataByteSize / 4);
463468
while (position < fieldLimit) {
464469
output.addInt(decodeFixed32(data, position));
465470
position += 4;
@@ -476,7 +481,12 @@ static int decodePackedFixed64List(
476481
throws InvalidProtocolBufferException {
477482
final LongArrayList output = (LongArrayList) list;
478483
position = decodeVarint32(data, position, registers);
479-
final int fieldLimit = position + registers.int1;
484+
final int packedDataByteSize = registers.int1;
485+
final int fieldLimit = position + packedDataByteSize;
486+
if (fieldLimit > data.length) {
487+
throw InvalidProtocolBufferException.truncatedMessage();
488+
}
489+
output.ensureCapacity(output.size() + packedDataByteSize / 8);
480490
while (position < fieldLimit) {
481491
output.addLong(decodeFixed64(data, position));
482492
position += 8;
@@ -493,7 +503,12 @@ static int decodePackedFloatList(
493503
throws InvalidProtocolBufferException {
494504
final FloatArrayList output = (FloatArrayList) list;
495505
position = decodeVarint32(data, position, registers);
496-
final int fieldLimit = position + registers.int1;
506+
final int packedDataByteSize = registers.int1;
507+
final int fieldLimit = position + packedDataByteSize;
508+
if (fieldLimit > data.length) {
509+
throw InvalidProtocolBufferException.truncatedMessage();
510+
}
511+
output.ensureCapacity(output.size() + packedDataByteSize / 4);
497512
while (position < fieldLimit) {
498513
output.addFloat(decodeFloat(data, position));
499514
position += 4;
@@ -510,7 +525,12 @@ static int decodePackedDoubleList(
510525
throws InvalidProtocolBufferException {
511526
final DoubleArrayList output = (DoubleArrayList) list;
512527
position = decodeVarint32(data, position, registers);
513-
final int fieldLimit = position + registers.int1;
528+
final int packedDataByteSize = registers.int1;
529+
final int fieldLimit = position + packedDataByteSize;
530+
if (fieldLimit > data.length) {
531+
throw InvalidProtocolBufferException.truncatedMessage();
532+
}
533+
output.ensureCapacity(output.size() + packedDataByteSize / 8);
514534
while (position < fieldLimit) {
515535
output.addDouble(decodeDouble(data, position));
516536
position += 8;

java/core/src/main/java/com/google/protobuf/BooleanArrayList.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,7 @@ public void add(int index, Boolean element) {
170170
public void addBoolean(boolean element) {
171171
ensureIsMutable();
172172
if (size == array.length) {
173-
// Resize to 1.5x the size
174-
int length = ((size * 3) / 2) + 1;
173+
int length = growSize(array.length);
175174
boolean[] newArray = new boolean[length];
176175

177176
System.arraycopy(array, 0, newArray, 0, size);
@@ -192,8 +191,7 @@ private void addBoolean(int index, boolean element) {
192191
// Shift everything over to make room
193192
System.arraycopy(array, index, array, index + 1, size - index);
194193
} else {
195-
// Resize to 1.5x the size
196-
int length = ((size * 3) / 2) + 1;
194+
int length = growSize(array.length);
197195
boolean[] newArray = new boolean[length];
198196

199197
// Copy the first part directly
@@ -255,6 +253,26 @@ public Boolean remove(int index) {
255253
return value;
256254
}
257255

256+
/** Ensures the backing array can fit at least minCapacity elements. */
257+
void ensureCapacity(int minCapacity) {
258+
if (minCapacity <= array.length) {
259+
return;
260+
}
261+
// To avoid quadratic copying when calling .addAllFoo(List) in a loop, we must not size to
262+
// exactly the requested capacity, but must exponentially grow instead. This is similar
263+
// behaviour to ArrayList.
264+
int n = array.length;
265+
while (n < minCapacity) {
266+
n = growSize(n);
267+
}
268+
array = Arrays.copyOf(array, n);
269+
}
270+
271+
private static int growSize(int previousSize) {
272+
// Resize to 1.5x the size
273+
return ((previousSize * 3) / 2) + 1;
274+
}
275+
258276
/**
259277
* Ensures that the provided {@code index} is within the range of {@code [0, size]}. Throws an
260278
* {@link IndexOutOfBoundsException} if it is not.

java/core/src/main/java/com/google/protobuf/DoubleArrayList.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,7 @@ public void add(int index, Double element) {
170170
public void addDouble(double element) {
171171
ensureIsMutable();
172172
if (size == array.length) {
173-
// Resize to 1.5x the size
174-
int length = ((size * 3) / 2) + 1;
173+
int length = growSize(array.length);
175174
double[] newArray = new double[length];
176175

177176
System.arraycopy(array, 0, newArray, 0, size);
@@ -192,8 +191,7 @@ private void addDouble(int index, double element) {
192191
// Shift everything over to make room
193192
System.arraycopy(array, index, array, index + 1, size - index);
194193
} else {
195-
// Resize to 1.5x the size
196-
int length = ((size * 3) / 2) + 1;
194+
int length = growSize(array.length);
197195
double[] newArray = new double[length];
198196

199197
// Copy the first part directly
@@ -255,6 +253,26 @@ public Double remove(int index) {
255253
return value;
256254
}
257255

256+
/** Ensures the backing array can fit at least minCapacity elements. */
257+
void ensureCapacity(int minCapacity) {
258+
if (minCapacity <= array.length) {
259+
return;
260+
}
261+
// To avoid quadratic copying when calling .addAllFoo(List) in a loop, we must not size to
262+
// exactly the requested capacity, but must exponentially grow instead. This is similar
263+
// behaviour to ArrayList.
264+
int n = array.length;
265+
while (n < minCapacity) {
266+
n = growSize(n);
267+
}
268+
array = Arrays.copyOf(array, n);
269+
}
270+
271+
private static int growSize(int previousSize) {
272+
// Resize to 1.5x the size
273+
return ((previousSize * 3) / 2) + 1;
274+
}
275+
258276
/**
259277
* Ensures that the provided {@code index} is within the range of {@code [0, size]}. Throws an
260278
* {@link IndexOutOfBoundsException} if it is not.

java/core/src/main/java/com/google/protobuf/FloatArrayList.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,7 @@ public void add(int index, Float element) {
169169
public void addFloat(float element) {
170170
ensureIsMutable();
171171
if (size == array.length) {
172-
// Resize to 1.5x the size
173-
int length = ((size * 3) / 2) + 1;
172+
int length = growSize(array.length);
174173
float[] newArray = new float[length];
175174

176175
System.arraycopy(array, 0, newArray, 0, size);
@@ -191,8 +190,7 @@ private void addFloat(int index, float element) {
191190
// Shift everything over to make room
192191
System.arraycopy(array, index, array, index + 1, size - index);
193192
} else {
194-
// Resize to 1.5x the size
195-
int length = ((size * 3) / 2) + 1;
193+
int length = growSize(array.length);
196194
float[] newArray = new float[length];
197195

198196
// Copy the first part directly
@@ -254,6 +252,26 @@ public Float remove(int index) {
254252
return value;
255253
}
256254

255+
/** Ensures the backing array can fit at least minCapacity elements. */
256+
void ensureCapacity(int minCapacity) {
257+
if (minCapacity <= array.length) {
258+
return;
259+
}
260+
// To avoid quadratic copying when calling .addAllFoo(List) in a loop, we must not size to
261+
// exactly the requested capacity, but must exponentially grow instead. This is similar
262+
// behaviour to ArrayList.
263+
int n = array.length;
264+
while (n < minCapacity) {
265+
n = growSize(n);
266+
}
267+
array = Arrays.copyOf(array, n);
268+
}
269+
270+
private static int growSize(int previousSize) {
271+
// Resize to 1.5x the size
272+
return ((previousSize * 3) / 2) + 1;
273+
}
274+
257275
/**
258276
* Ensures that the provided {@code index} is within the range of {@code [0, size]}. Throws an
259277
* {@link IndexOutOfBoundsException} if it is not.

java/core/src/main/java/com/google/protobuf/IntArrayList.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,7 @@ public void add(int index, Integer element) {
169169
public void addInt(int element) {
170170
ensureIsMutable();
171171
if (size == array.length) {
172-
// Resize to 1.5x the size
173-
int length = ((size * 3) / 2) + 1;
172+
int length = growSize(array.length);
174173
int[] newArray = new int[length];
175174

176175
System.arraycopy(array, 0, newArray, 0, size);
@@ -191,8 +190,7 @@ private void addInt(int index, int element) {
191190
// Shift everything over to make room
192191
System.arraycopy(array, index, array, index + 1, size - index);
193192
} else {
194-
// Resize to 1.5x the size
195-
int length = ((size * 3) / 2) + 1;
193+
int length = growSize(array.length);
196194
int[] newArray = new int[length];
197195

198196
// Copy the first part directly
@@ -254,6 +252,26 @@ public Integer remove(int index) {
254252
return value;
255253
}
256254

255+
/** Ensures the backing array can fit at least minCapacity elements. */
256+
void ensureCapacity(int minCapacity) {
257+
if (minCapacity <= array.length) {
258+
return;
259+
}
260+
// To avoid quadratic copying when calling .addAllFoo(List) in a loop, we must not size to
261+
// exactly the requested capacity, but must exponentially grow instead. This is similar
262+
// behaviour to ArrayList.
263+
int n = array.length;
264+
while (n < minCapacity) {
265+
n = growSize(n);
266+
}
267+
array = Arrays.copyOf(array, n);
268+
}
269+
270+
private static int growSize(int previousSize) {
271+
// Resize to 1.5x the size
272+
return ((previousSize * 3) / 2) + 1;
273+
}
274+
257275
/**
258276
* Ensures that the provided {@code index} is within the range of {@code [0, size]}. Throws an
259277
* {@link IndexOutOfBoundsException} if it is not.

java/core/src/main/java/com/google/protobuf/LongArrayList.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,7 @@ public void add(int index, Long element) {
169169
public void addLong(long element) {
170170
ensureIsMutable();
171171
if (size == array.length) {
172-
// Resize to 1.5x the size
173-
int length = ((size * 3) / 2) + 1;
172+
int length = growSize(array.length);
174173
long[] newArray = new long[length];
175174

176175
System.arraycopy(array, 0, newArray, 0, size);
@@ -191,8 +190,7 @@ private void addLong(int index, long element) {
191190
// Shift everything over to make room
192191
System.arraycopy(array, index, array, index + 1, size - index);
193192
} else {
194-
// Resize to 1.5x the size
195-
int length = ((size * 3) / 2) + 1;
193+
int length = growSize(array.length);
196194
long[] newArray = new long[length];
197195

198196
// Copy the first part directly
@@ -254,6 +252,26 @@ public Long remove(int index) {
254252
return value;
255253
}
256254

255+
/** Ensures the backing array can fit at least minCapacity elements. */
256+
void ensureCapacity(int minCapacity) {
257+
if (minCapacity <= array.length) {
258+
return;
259+
}
260+
// To avoid quadratic copying when calling .addAllFoo(List) in a loop, we must not size to
261+
// exactly the requested capacity, but must exponentially grow instead. This is similar
262+
// behaviour to ArrayList.
263+
int n = array.length;
264+
while (n < minCapacity) {
265+
n = growSize(n);
266+
}
267+
array = Arrays.copyOf(array, n);
268+
}
269+
270+
private static int growSize(int previousSize) {
271+
// Resize to 1.5x the size
272+
return ((previousSize * 3) / 2) + 1;
273+
}
274+
257275
/**
258276
* Ensures that the provided {@code index} is within the range of {@code [0, size]}. Throws an
259277
* {@link IndexOutOfBoundsException} if it is not.

java/core/src/test/java/com/google/protobuf/ArrayDecodersTest.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,42 @@ public void testDecodePackedFixed32List_negativeSize() {
159159
packedSizeBytesNoTag(-1), 0, new IntArrayList(), registers));
160160
}
161161

162+
@Test
163+
public void testDecodePackedFixed32List_2gb_beyondEndOfArray() {
164+
assertThrows(
165+
InvalidProtocolBufferException.class,
166+
() ->
167+
ArrayDecoders.decodePackedFixed32List(
168+
packedSizeBytesNoTag(2_000_000_000), 0, new IntArrayList(), registers));
169+
}
170+
171+
@Test
172+
public void testDecodePackedFixed64List_2gb_beyondEndOfArray() {
173+
assertThrows(
174+
InvalidProtocolBufferException.class,
175+
() ->
176+
ArrayDecoders.decodePackedFixed64List(
177+
packedSizeBytesNoTag(2_000_000_000), 0, new LongArrayList(), registers));
178+
}
179+
180+
@Test
181+
public void testDecodePackedFloatList_2gb_beyondEndOfArray() {
182+
assertThrows(
183+
InvalidProtocolBufferException.class,
184+
() ->
185+
ArrayDecoders.decodePackedFloatList(
186+
packedSizeBytesNoTag(2_000_000_000), 0, new FloatArrayList(), registers));
187+
}
188+
189+
@Test
190+
public void testDecodePackedDoubleList_2gb_beyondEndOfArray() {
191+
assertThrows(
192+
InvalidProtocolBufferException.class,
193+
() ->
194+
ArrayDecoders.decodePackedDoubleList(
195+
packedSizeBytesNoTag(2_000_000_000), 0, new DoubleArrayList(), registers));
196+
}
197+
162198
@Test
163199
public void testDecodePackedFixed64List_negativeSize() {
164200
assertThrows(

0 commit comments

Comments
 (0)