Skip to content

Commit a08422f

Browse files
committed
When decoding corrupted BSON, the driver should throw BsonSerializationException rather than other runtime exceptions.
JAVA-1864
1 parent 9c4cc77 commit a08422f

File tree

3 files changed

+125
-5
lines changed

3 files changed

+125
-5
lines changed

bson/src/main/org/bson/io/ByteBufferBsonInput.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@
1616

1717
package org.bson.io;
1818

19+
import org.bson.BsonSerializationException;
1920
import org.bson.ByteBuf;
2021
import org.bson.types.ObjectId;
2122

2223
import java.nio.ByteOrder;
2324
import java.nio.charset.Charset;
2425

26+
import static java.lang.String.format;
27+
2528
/**
2629
* An implementation of {@code BsonInput} that is backed by a {@code ByteBuf}.
2730
*
@@ -57,45 +60,58 @@ public int getPosition() {
5760
@Override
5861
public byte readByte() {
5962
ensureOpen();
63+
ensureAvailable(1);
6064
return buffer.get();
6165
}
6266

6367
@Override
6468
public void readBytes(final byte[] bytes) {
6569
ensureOpen();
70+
ensureAvailable(bytes.length);
6671
buffer.get(bytes);
6772
}
6873

6974
@Override
7075
public void readBytes(final byte[] bytes, final int offset, final int length) {
7176
ensureOpen();
77+
ensureAvailable(length);
7278
buffer.get(bytes, offset, length);
7379
}
7480

7581
@Override
7682
public long readInt64() {
7783
ensureOpen();
84+
ensureAvailable(8);
7885
return buffer.getLong();
7986
}
8087

8188
@Override
8289
public double readDouble() {
8390
ensureOpen();
91+
ensureAvailable(8);
8492
return buffer.getDouble();
8593
}
8694

8795
@Override
8896
public int readInt32() {
8997
ensureOpen();
98+
ensureAvailable(4);
9099
return buffer.getInt();
91100
}
92101

93102
@Override
94103
public String readString() {
95104
ensureOpen();
96105
int size = readInt32();
106+
if (size <= 0) {
107+
throw new BsonSerializationException(format("While decoding a BSON string found a size that is not a positive number: %d",
108+
size));
109+
}
97110
byte[] bytes = new byte[size];
98111
readBytes(bytes);
112+
if (bytes[size - 1] != 0) {
113+
throw new BsonSerializationException("Found a BSON string that is not null-terminated");
114+
}
99115
return new String(bytes, 0, size - 1, UTF8_CHARSET);
100116
}
101117

@@ -126,7 +142,7 @@ public String readCString() {
126142

127143
private void readUntilNullByte() {
128144
//CHECKSTYLE:OFF
129-
while (buffer.get() != 0) { //NOPMD
145+
while (readByte() != 0) { //NOPMD
130146
//do nothing - checkstyle & PMD hate this, not surprisingly
131147
}
132148
//CHECKSTYLE:ON
@@ -176,4 +192,10 @@ private void ensureOpen() {
176192
throw new IllegalStateException("Stream is closed");
177193
}
178194
}
195+
private void ensureAvailable(final int bytesNeeded) {
196+
if (buffer.remaining() < bytesNeeded) {
197+
throw new BsonSerializationException(format("While decoding a BSON document %d bytes were required, "
198+
+ "but only %d remain", bytesNeeded, buffer.remaining()));
199+
}
200+
}
179201
}

bson/src/test/unit/org/bson/io/ByteBufferBsonInputSpecification.groovy

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.bson.io
1818

19+
import org.bson.BsonSerializationException
1920
import org.bson.ByteBufNIO
2021
import org.bson.types.ObjectId
2122
import spock.lang.Specification
@@ -254,4 +255,102 @@ class ByteBufferBsonInputSpecification extends Specification {
254255
thrown(IllegalStateException)
255256
}
256257

257-
}
258+
def 'should throw BsonSerializationException reading a byte if no byte is available'() {
259+
given:
260+
def stream = new ByteBufferBsonInput(new ByteBufNIO(ByteBuffer.wrap([] as byte[])))
261+
262+
when:
263+
stream.readByte()
264+
265+
then:
266+
thrown(BsonSerializationException)
267+
}
268+
269+
def 'should throw BsonSerializationException reading an Int32 if less than 4 bytes are available'() {
270+
given:
271+
def stream = new ByteBufferBsonInput(new ByteBufNIO(ByteBuffer.wrap([0, 0, 0] as byte[])))
272+
273+
when:
274+
stream.readInt32()
275+
276+
then:
277+
thrown(BsonSerializationException)
278+
}
279+
280+
def 'should throw BsonSerializationException reading an Int64 if less than 8 bytes are available'() {
281+
given:
282+
def stream = new ByteBufferBsonInput(new ByteBufNIO(ByteBuffer.wrap([0, 0, 0, 0, 0, 0, 0] as byte[])))
283+
284+
when:
285+
stream.readInt64()
286+
287+
then:
288+
thrown(BsonSerializationException)
289+
}
290+
291+
def 'should throw BsonSerializationException reading a double if less than 8 bytes are available'() {
292+
given:
293+
def stream = new ByteBufferBsonInput(new ByteBufNIO(ByteBuffer.wrap([0, 0, 0, 0, 0, 0, 0] as byte[])))
294+
295+
when:
296+
stream.readDouble()
297+
298+
then:
299+
thrown(BsonSerializationException)
300+
}
301+
302+
def 'should throw BsonSerializationException reading an ObjectId if less than 12 bytes are available'() {
303+
given:
304+
def stream = new ByteBufferBsonInput(new ByteBufNIO(ByteBuffer.wrap([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] as byte[])))
305+
306+
when:
307+
stream.readObjectId()
308+
309+
then:
310+
thrown(BsonSerializationException)
311+
}
312+
313+
def 'should throw BsonSerializationException reading into a byte array if not enough bytes are available'() {
314+
given:
315+
def stream = new ByteBufferBsonInput(new ByteBufNIO(ByteBuffer.wrap([0, 0, 0, 0, 0, 0, 0] as byte[])))
316+
317+
when:
318+
stream.readBytes(new byte[8])
319+
320+
then:
321+
thrown(BsonSerializationException)
322+
}
323+
324+
def 'should throw BsonSerializationException reading partially into a byte array if not enough bytes are available'() {
325+
given:
326+
def stream = new ByteBufferBsonInput(new ByteBufNIO(ByteBuffer.wrap([0, 0, 0, 0] as byte[])))
327+
328+
when:
329+
stream.readBytes(new byte[8], 2, 5)
330+
331+
then:
332+
thrown(BsonSerializationException)
333+
}
334+
335+
def 'should throw BsonSerializationException if the length of a BSON string is not positive'() {
336+
given:
337+
def stream = new ByteBufferBsonInput(new ByteBufNIO(ByteBuffer.wrap([-1, -1, -1, -1, 41, 42, 43, 0] as byte[])))
338+
339+
when:
340+
stream.readString()
341+
342+
then:
343+
thrown(BsonSerializationException)
344+
}
345+
346+
def 'should throw BsonSerializationException if a BSON string is not null-terminated'() {
347+
given:
348+
def stream = new ByteBufferBsonInput(new ByteBufNIO(ByteBuffer.wrap([4, 0, 0, 0, 41, 42, 43, 99] as byte[])))
349+
350+
when:
351+
stream.readString()
352+
353+
then:
354+
thrown(BsonSerializationException)
355+
}
356+
}

driver/src/test/unit/org/bson/BasicBSONDecoderSpecification.groovy

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import spock.lang.Specification
2828
import spock.lang.Subject
2929
import spock.lang.Unroll
3030

31-
import java.nio.BufferUnderflowException
3231
import java.util.regex.Pattern
3332

3433
@SuppressWarnings(['LineLength', 'DuplicateMapLiteral', 'UnnecessaryBooleanExpression'])
@@ -159,9 +158,9 @@ class BasicBSONDecoderSpecification extends Specification {
159158
where:
160159
exception | bytes
161160
BsonSerializationException | [13, 0, 0, 0, 16, 97, 0, 1, 0, 0, 0, 0]
162-
BufferUnderflowException | [12, 0, 0, 0, 17, 97, 0, 1, 0, 0, 0, 0]
161+
BsonSerializationException | [12, 0, 0, 0, 17, 97, 0, 1, 0, 0, 0, 0]
163162
BsonSerializationException | [12, 0, 2, 0, 16, 97, 0, 1, 0, 0, 0, 0]
164163
BsonSerializationException | [5, 0, 0, 0, 16, 97, 0, 1, 0, 0, 0, 0]
165-
BufferUnderflowException | [5, 0, 0, 0, 16, 97, 45, 1, 0, 0, 0, 0]
164+
BsonSerializationException | [5, 0, 0, 0, 16, 97, 45, 1, 0, 0, 0, 0]
166165
}
167166
}

0 commit comments

Comments
 (0)