Skip to content

Commit 64b7577

Browse files
authored
Merge pull request #37 from ChainSafe/tuyen/fix-fuzzing-tests
Validation for fromBytes
2 parents 7471047 + 18b4be4 commit 64b7577

File tree

13 files changed

+107
-11
lines changed

13 files changed

+107
-11
lines changed

src/backings/structural/abstract.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,37 @@ export class StructuralHandler<T extends object> {
3434
equals(target: T, other: T): boolean {
3535
throw new Error("Not implemented");
3636
}
37+
validateBytes(data: Uint8Array, start: number, end: number): void {
38+
if (!data) {
39+
throw new Error("Data is null or undefined");
40+
}
41+
if (data.length === 0) {
42+
throw new Error("Data is empty");
43+
}
44+
if (start < 0) {
45+
throw new Error(`Start param is negative: ${start}`);
46+
}
47+
if (start >= data.length) {
48+
throw new Error(`Start param: ${start} is greater than length: ${data.length}`);
49+
}
50+
if (end < 0) {
51+
throw new Error(`End param is negative: ${end}`);
52+
}
53+
if (end > data.length) {
54+
throw new Error(`End param: ${end} is greater than length: ${data.length}`);
55+
}
56+
const length = end - start;
57+
if (!this._type.isVariableSize() && length !== this.size(null)) {
58+
throw new Error(`Incorrect data length ${length}, expect ${this.size(null)}`);
59+
}
60+
if (end - start < this.minSize()) {
61+
throw new Error(`Data length ${length} is too small, expect at least ${this.minSize()}`);
62+
}
63+
}
3764
fromBytes(data: Uint8Array, start: number, end: number): T {
3865
throw new Error("Not implemented");
3966
}
4067
deserialize(data: Uint8Array): T {
41-
if (!this._type.isVariableSize() && this.size(null) !== data.length) {
42-
throw new Error("Incorrect data length");
43-
}
4468
return this.fromBytes(data, 0, data.length);
4569
}
4670
serialize(target: T): Uint8Array {

src/backings/structural/array.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export class BasicArrayStructuralHandler<T extends ArrayLike<unknown>> extends S
4848
return newValue;
4949
}
5050
fromBytes(data: Uint8Array, start: number, end: number): T {
51+
this.validateBytes(data, start, end);
5152
const elementSize = this._type.elementType.size();
5253
return Array.from(
5354
{length: (end - start) / elementSize},
@@ -151,6 +152,7 @@ export class CompositeArrayStructuralHandler<T extends ArrayLike<object>> extend
151152
return newValue;
152153
}
153154
fromBytes(data: Uint8Array, start: number, end: number): T {
155+
this.validateBytes(data, start, end);
154156
if (start === end) {
155157
return [] as unknown as T;
156158
}

src/backings/structural/bitList.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export class BitListStructuralHandler extends BasicListStructuralHandler<BitList
3838
return 1;
3939
}
4040
fromBytes(data: Uint8Array, start: number, end: number): BitList {
41+
this.validateBytes(data, start, end);
4142
const value = [];
4243
const toBool = (c: string): boolean => c === "1" ? true : false;
4344
for (let i = start; i < end-1; i++) {

src/backings/structural/bitVector.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export class BitVectorStructuralHandler extends BasicVectorStructuralHandler<Bit
3535
return Number(bitstring);
3636
}
3737
fromBytes(data: Uint8Array, start: number, end: number): BitVector {
38+
this.validateBytes(data, start, end);
3839
if ((end - start) !== this.size(null)) {
3940
throw new Error("Invalid bitvector: length not equal to vector length");
4041
}

src/backings/structural/byteVector.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export class ByteVectorStructuralHandler extends BasicVectorStructuralHandler<By
1313
return new Uint8Array(this._type.length);
1414
}
1515
fromBytes(data: Uint8Array, start: number, end: number): ByteVector {
16+
this.validateBytes(data, start, end);
1617
const length = end - start;
1718
if (length !== this._type.length) {
1819
throw new Error(`Invalid deserialized vector length: expected ${this._type.length}, actual: ${length}`);

src/backings/structural/container.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ export class ContainerStructuralHandler<T extends ObjectLike> extends Structural
8282
return newValue;
8383
}
8484
fromBytes(data: Uint8Array, start: number, end: number): T {
85+
this.validateBytes(data, start, end);
8586
let currentIndex = start;
8687
let nextIndex = currentIndex;
8788
const value = {} as T;

src/backings/structural/list.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,14 @@ export class BasicListStructuralHandler<T extends List<unknown>> extends BasicAr
2121
getMinLength(): number {
2222
return 0;
2323
}
24-
fromBytes(data: Uint8Array, start: number, end: number): T {
24+
validateBytes(data: Uint8Array, start: number, end: number): void {
25+
super.validateBytes(data, start, end);
2526
if ((end - start) / this._type.elementType.size() > this._type.limit) {
2627
throw new Error("Deserialized list length greater than limit");
2728
}
29+
}
30+
fromBytes(data: Uint8Array, start: number, end: number): T {
31+
this.validateBytes(data, start, end);
2832
return super.fromBytes(data, start, end);
2933
}
3034
nonzeroChunkCount(value: T): number {
@@ -64,6 +68,7 @@ export class CompositeListStructuralHandler<T extends List<object>> extends Comp
6468
return 0;
6569
}
6670
fromBytes(data: Uint8Array, start: number, end: number): T {
71+
this.validateBytes(data, start, end);
6772
const value = super.fromBytes(data, start, end);
6873
if (value.length > this._type.limit) {
6974
throw new Error("Deserialized list length greater than limit");

src/backings/structural/vector.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,14 @@ export class BasicVectorStructuralHandler<T extends Vector<unknown>> extends Bas
2323
getMinLength(): number {
2424
return this._type.length;
2525
}
26-
fromBytes(data: Uint8Array, start: number, end: number): T {
26+
validateBytes(data: Uint8Array, start: number, end: number): void {
27+
super.validateBytes(data, start, end);
2728
if ((end - start) / this._type.elementType.size() !== this._type.length) {
2829
throw new Error("Incorrect deserialized vector length");
2930
}
31+
}
32+
fromBytes(data: Uint8Array, start: number, end: number): T {
33+
this.validateBytes(data, start, end);
3034
return super.fromBytes(data, start, end);
3135
}
3236
assertValidValue(value: unknown): asserts value is T {
@@ -71,6 +75,7 @@ export class CompositeVectorStructuralHandler<T extends Vector<object>> extends
7175
return this._type.length;
7276
}
7377
fromBytes(data: Uint8Array, start: number, end: number): T {
78+
this.validateBytes(data, start, end);
7479
const value = super.fromBytes(data, start, end);
7580
if (value.length !== this._type.length) {
7681
throw new Error("Incorrect deserialized vector length");

src/types/basic/abstract.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,25 @@ export class BasicType<T> {
9797
return this.size();
9898
}
9999

100+
/**
101+
* Validate bytes before calling fromBytes
102+
* @param data
103+
* @param offset
104+
*/
105+
validateBytes(data: Uint8Array, offset: number): void {
106+
if (!data) {
107+
throw new Error("Data is null or undefined");
108+
}
109+
if (data.length === 0) {
110+
throw new Error("Data is empty");
111+
}
112+
const length = data.length - offset;
113+
if (length < this.size()) {
114+
throw new Error(`Data length of ${length} is too small, expect ${this.size()}`);
115+
}
116+
// accept data length > this.size()
117+
}
118+
100119
/**
101120
* Low-level deserialization
102121
*/

src/types/basic/boolean.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export class BooleanType extends BasicType<boolean> {
3131
return offset + 1;
3232
}
3333
fromBytes(data: Uint8Array, offset: number): boolean {
34+
this.validateBytes(data, offset);
3435
if (data[offset] === 1) {
3536
return true;
3637
} else if (data[offset] === 0) {

src/types/basic/uint.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export class NumberUintType extends UintType<number> {
7171
return offset + this.byteLength;
7272
}
7373
fromBytes(data: Uint8Array, offset: number): number {
74+
this.validateBytes(data, offset);
7475
let isInfinity = true;
7576
let output = BigInt(0);
7677
for (let i = 0; i < this.byteLength; i++) {
@@ -139,6 +140,7 @@ export class BigIntUintType extends UintType<bigint> {
139140
return offset + this.byteLength;
140141
}
141142
fromBytes(data: Uint8Array, offset: number): bigint {
143+
this.validateBytes(data, offset);
142144
let output = BigInt(0);
143145
for (let i = 0; i < this.byteLength; i++) {
144146
output += BigInt(data[offset + i]) << BigInt(8 * i);

test/unit/deserialize.test.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import {assert} from "chai";
1+
import {assert, expect} from "chai";
22
import {describe, it} from "mocha";
33

44
import {booleanType, byteType, ContainerType} from "../../src";
55
import {
6-
ArrayObject, ArrayObject2, bigint16Type, bigint64Type, bigint128Type, bigint256Type, bitList100Type, bitVector100Type, byteVector100Type,
7-
bytes2Type, bytes8Type, bytes32Type,
8-
number16Type, number32Type, number64Type, number16Vector6Type, number16List100Type, OuterObject, SimpleObject
6+
ArrayObject, ArrayObject2, bigint64Type, bigint128Type, bigint256Type,
7+
bytes8Type, bytes32Type,
8+
number16Type, number32Type, number64Type, number16Vector6Type, number16List100Type, OuterObject, SimpleObject, VariableSizeSimpleObject
99
} from "./objects";
1010

1111

@@ -69,6 +69,32 @@ describe("deserialize", () => {
6969
assert.deepEqual(actual, expected);
7070
});
7171
}
72+
const invalidDataTestCases: {
73+
value: string;
74+
type: any;
75+
expectedError: string;
76+
}[] = [
77+
{value: "", type: number16Type, expectedError: "Data is empty"},
78+
{value: "00", type: number16Type, expectedError: "Data length of 1 is too small, expect 2"},
79+
{value: "", type: number16Vector6Type, expectedError: "Data is empty"},
80+
{value: "00", type: number16Vector6Type, expectedError: "Incorrect data length 1, expect 12"},
81+
{value: "", type: number16List100Type, expectedError: "Data is empty"},
82+
{value: "", type: SimpleObject, expectedError: "Data is empty"},
83+
{value: "00", type: SimpleObject, expectedError: "Incorrect data length 1, expect 3"},
84+
{value: "", type: VariableSizeSimpleObject, expectedError: "Data is empty"},
85+
{value: "00", type: VariableSizeSimpleObject, expectedError: "Data length 1 is too small, expect at least 7"},
86+
];
87+
for (const {type, value, expectedError} of invalidDataTestCases) {
88+
it(`should throw error deserializing invalid data for ${type.constructor.name}`, () => {
89+
try {
90+
type.deserialize(Buffer.from(value, "hex"));
91+
assert.fail("Expect error here");
92+
} catch (e) {
93+
expect(e.message).to.be.equal(expectedError);
94+
}
95+
});
96+
}
97+
7298
});
7399

74100
interface ITestType {

test/unit/objects.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export const bytes8Type = new ByteVectorType({
2525
});
2626

2727
export const bytes32Type = new ByteVectorType({
28-
length: 32
28+
length: 32
2929
});
3030

3131
export const byteVector100Type = new ByteVectorType({length: 100});
@@ -55,7 +55,7 @@ export const number16Vector6Type = new VectorType({
5555

5656
export const number16List100Type = new ListType({
5757
elementType: number16Type,
58-
limit:100
58+
limit:100
5959
});
6060

6161
export const SimpleObject = new ContainerType({
@@ -65,6 +65,14 @@ export const SimpleObject = new ContainerType({
6565
},
6666
});
6767

68+
export const VariableSizeSimpleObject = new ContainerType({
69+
fields: {
70+
b: number16Type,
71+
a: byteType,
72+
list: number16List100Type,
73+
},
74+
});
75+
6876
export const CamelCaseFieldObject = new ContainerType({
6977
fields: {
7078
someValue: new NumberUintType({byteLength: 4}),

0 commit comments

Comments
 (0)