Skip to content

Commit 0dafaf3

Browse files
committed
fix(NODE-6536): Binary.read never returns number[] and reads beyond content
1 parent 887849d commit 0dafaf3

File tree

4 files changed

+69
-55
lines changed

4 files changed

+69
-55
lines changed

src/binary.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@ export class Binary extends BSONValue {
6161
/** User BSON type */
6262
static readonly SUBTYPE_USER_DEFINED = 128;
6363

64-
buffer!: Uint8Array;
65-
sub_type!: number;
66-
position!: number;
64+
public buffer: Uint8Array;
65+
public sub_type: number;
66+
public position: number;
6767

6868
/**
6969
* Create a new Binary instance.
@@ -160,16 +160,15 @@ export class Binary extends BSONValue {
160160
}
161161

162162
/**
163-
* Reads **length** bytes starting at **position**.
163+
* Returns a view of **length** bytes starting at **position**.
164164
*
165165
* @param position - read from the given position in the Binary.
166166
* @param length - the number of bytes to read.
167167
*/
168-
read(position: number, length: number): BinarySequence {
168+
read(position: number, length: number): Uint8Array {
169169
length = length && length > 0 ? length : this.position;
170-
171-
// Let's return the data based on the type we have
172-
return this.buffer.slice(position, position + length);
170+
const end = position + length;
171+
return this.buffer.subarray(position, end > this.position ? this.position : end);
173172
}
174173

175174
/** returns a view of the binary value as a Uint8Array */
@@ -219,7 +218,7 @@ export class Binary extends BSONValue {
219218

220219
toUUID(): UUID {
221220
if (this.sub_type === Binary.SUBTYPE_UUID) {
222-
return new UUID(this.buffer.slice(0, this.position));
221+
return new UUID(this.buffer.subarray(0, this.position));
223222
}
224223

225224
throw new BSONError(

src/parser/deserializer.ts

Lines changed: 17 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ function deserializeObject(
296296

297297
// We have a raw value
298298
if (raw) {
299-
value = buffer.slice(index, index + objectSize);
299+
value = buffer.subarray(index, index + objectSize);
300300
} else {
301301
let objectOptions = options;
302302
if (!globalUTFValidation) {
@@ -374,52 +374,24 @@ function deserializeObject(
374374
if (binarySize > buffer.byteLength)
375375
throw new BSONError('Binary type size larger than document size');
376376

377-
// Decode as raw Buffer object if options specifies it
378-
if (buffer['slice'] != null) {
379-
// If we have subtype 2 skip the 4 bytes for the size
380-
if (subType === Binary.SUBTYPE_BYTE_ARRAY) {
381-
binarySize = NumberUtils.getInt32LE(buffer, index);
382-
index += 4;
383-
if (binarySize < 0)
384-
throw new BSONError('Negative binary type element size found for subtype 0x02');
385-
if (binarySize > totalBinarySize - 4)
386-
throw new BSONError('Binary type with subtype 0x02 contains too long binary size');
387-
if (binarySize < totalBinarySize - 4)
388-
throw new BSONError('Binary type with subtype 0x02 contains too short binary size');
389-
}
377+
// If we have subtype 2 skip the 4 bytes for the size
378+
if (subType === Binary.SUBTYPE_BYTE_ARRAY) {
379+
binarySize = NumberUtils.getInt32LE(buffer, index);
380+
index += 4;
381+
if (binarySize < 0)
382+
throw new BSONError('Negative binary type element size found for subtype 0x02');
383+
if (binarySize > totalBinarySize - 4)
384+
throw new BSONError('Binary type with subtype 0x02 contains too long binary size');
385+
if (binarySize < totalBinarySize - 4)
386+
throw new BSONError('Binary type with subtype 0x02 contains too short binary size');
387+
}
390388

391-
if (promoteBuffers && promoteValues) {
392-
value = ByteUtils.toLocalBufferType(buffer.slice(index, index + binarySize));
393-
} else {
394-
value = new Binary(buffer.slice(index, index + binarySize), subType);
395-
if (subType === constants.BSON_BINARY_SUBTYPE_UUID_NEW && UUID.isValid(value)) {
396-
value = value.toUUID();
397-
}
398-
}
389+
if (promoteBuffers && promoteValues) {
390+
value = ByteUtils.toLocalBufferType(buffer.subarray(index, index + binarySize));
399391
} else {
400-
// If we have subtype 2 skip the 4 bytes for the size
401-
if (subType === Binary.SUBTYPE_BYTE_ARRAY) {
402-
binarySize = NumberUtils.getInt32LE(buffer, index);
403-
index += 4;
404-
if (binarySize < 0)
405-
throw new BSONError('Negative binary type element size found for subtype 0x02');
406-
if (binarySize > totalBinarySize - 4)
407-
throw new BSONError('Binary type with subtype 0x02 contains too long binary size');
408-
if (binarySize < totalBinarySize - 4)
409-
throw new BSONError('Binary type with subtype 0x02 contains too short binary size');
410-
}
411-
412-
if (promoteBuffers && promoteValues) {
413-
value = ByteUtils.allocateUnsafe(binarySize);
414-
// Copy the data
415-
for (i = 0; i < binarySize; i++) {
416-
value[i] = buffer[index + i];
417-
}
418-
} else {
419-
value = new Binary(buffer.slice(index, index + binarySize), subType);
420-
if (subType === constants.BSON_BINARY_SUBTYPE_UUID_NEW && UUID.isValid(value)) {
421-
value = value.toUUID();
422-
}
392+
value = new Binary(buffer.subarray(index, index + binarySize), subType);
393+
if (subType === constants.BSON_BINARY_SUBTYPE_UUID_NEW && UUID.isValid(value)) {
394+
value = value.toUUID();
423395
}
424396
}
425397

test/node/binary.test.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { expect } from 'chai';
22
import * as vm from 'node:vm';
3-
import { Binary, BSON } from '../register-bson';
3+
import { __isWeb__, Binary, BSON } from '../register-bson';
44
import * as util from 'node:util';
55

66
describe('class Binary', () => {
@@ -81,6 +81,46 @@ describe('class Binary', () => {
8181
});
8282
});
8383

84+
describe('read()', function () {
85+
const LocalBuffer = __isWeb__ ? Uint8Array : Buffer;
86+
87+
it('reads a single byte from the buffer', function () {
88+
const binary = new Binary();
89+
binary.put(0x42);
90+
expect(binary.read(0, 1)).to.deep.equal(LocalBuffer.of(0x42));
91+
});
92+
93+
it('does not read beyond binary.position', function () {
94+
const binary = new Binary();
95+
binary.put(0x42);
96+
expect(binary.buffer.byteLength).to.equal(256);
97+
expect(binary.read(0, 10)).to.deep.equal(LocalBuffer.of(0x42));
98+
});
99+
100+
it('reads a single byte from the buffer at the given position', function () {
101+
const binary = new Binary();
102+
binary.put(0x42);
103+
binary.put(0x43);
104+
binary.put(0x44);
105+
expect(binary.read(1, 1)).to.deep.equal(LocalBuffer.of(0x43));
106+
});
107+
108+
it('reads nothing if the position is out of bounds', function () {
109+
const binary = new Binary();
110+
expect(binary.read(1, 0)).to.have.lengthOf(0);
111+
});
112+
113+
it('sets length to position if not provided', function () {
114+
const binary = new Binary();
115+
binary.put(0x42);
116+
binary.put(0x42);
117+
binary.put(0x42);
118+
expect(binary.position).to.equal(3);
119+
// @ts-expect-error: checking behavior TS doesn't support
120+
expect(binary.read(0)).to.have.lengthOf(3);
121+
});
122+
});
123+
84124
context('inspect()', () => {
85125
it(`when value is default returns "Binary.createFromBase64('', 0)"`, () => {
86126
expect(util.inspect(new Binary())).to.equal(`Binary.createFromBase64('', 0)`);
@@ -93,6 +133,7 @@ describe('class Binary', () => {
93133
});
94134

95135
it(`when value is default with a subtype returns "Binary.createFromBase64('', 35)"`, () => {
136+
// @ts-expect-error: check null is handled the same as undefined
96137
expect(util.inspect(new Binary(null, 0x23))).to.equal(`Binary.createFromBase64('', 35)`);
97138
});
98139

test/types/bson.test-d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,5 @@ expectNotDeprecated(new ObjectId(42 as string | number));
8585

8686
// Timestamp accepts timestamp because constructor allows: {i:number, t:number}
8787
new Timestamp(new Timestamp(0n))
88+
89+
expectType<(position: number, length: number) => Uint8Array>(Binary.prototype.read);

0 commit comments

Comments
 (0)