Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 199 additions & 0 deletions dart/packages/fory-test/test/config_test/size_guard_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

library;

import 'dart:typed_data';
import 'package:fory/fory.dart';
import 'package:fory/src/exception/fory_exception.dart';
import 'package:test/test.dart';

void main() {
group('maxCollectionSize guard check', () {
test('list within limit deserializes successfully', () {
final foryWrite = Fory();
final bytes = foryWrite.serialize([1, 2, 3]);

final foryRead = Fory(maxCollectionSize: 10);
final result = foryRead.deserialize(bytes);
expect(result, equals([1, 2, 3]));
});

test('list exceeding limit throws InvalidDataException', () {
final foryWrite = Fory();
final bytes = foryWrite.serialize([1, 2, 3, 4, 5]);

final foryRead = Fory(maxCollectionSize: 3);
expect(
() => foryRead.deserialize(bytes),
throwsA(isA<InvalidDataException>()),
);
});

test('list at exact limit deserializes successfully', () {
final foryWrite = Fory();
final bytes = foryWrite.serialize([1, 2, 3]);

final foryRead = Fory(maxCollectionSize: 3);
final result = foryRead.deserialize(bytes);
expect(result, equals([1, 2, 3]));
});

test('empty list always deserializes successfully', () {
final foryWrite = Fory();
final bytes = foryWrite.serialize(<int>[]);

final foryRead = Fory(maxCollectionSize: 0);
final result = foryRead.deserialize(bytes);
expect(result, equals([]));
});

test('map exceeding limit throws InvalidDataException', () {
final foryWrite = Fory();
final bytes = foryWrite.serialize({'a': 1, 'b': 2, 'c': 3});

final foryRead = Fory(maxCollectionSize: 2);
expect(
() => foryRead.deserialize(bytes),
throwsA(isA<InvalidDataException>()),
);
});

test('map within limit deserializes successfully', () {
final foryWrite = Fory();
final bytes = foryWrite.serialize({'a': 1, 'b': 2});

final foryRead = Fory(maxCollectionSize: 10);
final result = foryRead.deserialize(bytes);
expect(result, equals({'a': 1, 'b': 2}));
});

test('set exceeding limit throws InvalidDataException', () {
final foryWrite = Fory();
final bytes = foryWrite.serialize({1, 2, 3, 4, 5});

final foryRead = Fory(maxCollectionSize: 2);
expect(
() => foryRead.deserialize(bytes),
throwsA(isA<InvalidDataException>()),
);
});

test('set within limit deserializes successfully', () {
final foryWrite = Fory();
final bytes = foryWrite.serialize({1, 2, 3});

final foryRead = Fory(maxCollectionSize: 10);
final result = foryRead.deserialize(bytes);
expect(result, equals({1, 2, 3}));
});

test('default maxCollectionSize allows normal sizes', () {
final foryWrite = Fory();
final largeList = List.generate(1000, (i) => i);
final bytes = foryWrite.serialize(largeList);

final foryRead = Fory();
final result = foryRead.deserialize(bytes) as List;
expect(result.length, 1000);
});
});

group('maxBinarySize guard check', () {
test('binary within limit deserializes successfully', () {
final foryWrite = Fory();
final data = Uint8List.fromList([1, 2, 3, 4, 5]);
final bytes = foryWrite.serialize(data);

final foryRead = Fory(maxBinarySize: 10);
final result = foryRead.deserialize(bytes) as Uint8List;
expect(result, equals(data));
});

test('binary exceeding limit throws InvalidDataException', () {
final foryWrite = Fory();
final data = Uint8List.fromList([1, 2, 3, 4, 5]);
final bytes = foryWrite.serialize(data);

final foryRead = Fory(maxBinarySize: 3);
expect(
() => foryRead.deserialize(bytes),
throwsA(isA<InvalidDataException>()),
);
});

test('binary at exact limit deserializes successfully', () {
final foryWrite = Fory();
final data = Uint8List.fromList([1, 2, 3]);
final bytes = foryWrite.serialize(data);

final foryRead = Fory(maxBinarySize: 3);
final result = foryRead.deserialize(bytes) as Uint8List;
expect(result, equals(data));
});

test('empty binary always deserializes successfully', () {
final foryWrite = Fory();
final data = Uint8List(0);
final bytes = foryWrite.serialize(data);

final foryRead = Fory(maxBinarySize: 0);
final result = foryRead.deserialize(bytes) as Uint8List;
expect(result, equals(data));
});

test('default maxBinarySize allows normal sizes', () {
final foryWrite = Fory();
final data = Uint8List.fromList(List.generate(1000, (i) => i % 256));
final bytes = foryWrite.serialize(data);

final foryRead = Fory();
final result = foryRead.deserialize(bytes) as Uint8List;
expect(result.length, 1000);
});
});

group('combined guard check', () {
test('both limits enforced independently', () {
final foryWrite = Fory();

final listBytes = foryWrite.serialize([1, 2, 3, 4, 5]);
final binaryBytes =
foryWrite.serialize(Uint8List.fromList([1, 2, 3, 4, 5]));

final foryRead = Fory(maxCollectionSize: 3, maxBinarySize: 10);

// Collection exceeds limit
expect(
() => foryRead.deserialize(listBytes),
throwsA(isA<InvalidDataException>()),
);

// Binary within limit
final result = foryRead.deserialize(binaryBytes) as Uint8List;
expect(result.length, 5);
});

test('default values are applied', () {
final config = ForyConfig();
expect(config.maxCollectionSize, ForyConfig.defaultMaxCollectionSize);
expect(config.maxBinarySize, ForyConfig.defaultMaxBinarySize);
});
});
}
7 changes: 7 additions & 0 deletions dart/packages/fory/lib/src/config/fory_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,19 @@ final class ForyConfig {
final bool basicTypesRefIgnored;
final bool timeRefIgnored;
final bool stringRefIgnored;
final int maxBinarySize;
final int maxCollectionSize;

static const int defaultMaxBinarySize = 64 * 1024 * 1024;
static const int defaultMaxCollectionSize = 1000000;

const ForyConfig({
this.compatible = false,
this.ref = false,
this.basicTypesRefIgnored = true,
this.timeRefIgnored = true,
this.stringRefIgnored = false,
this.maxBinarySize = defaultMaxBinarySize,
this.maxCollectionSize = defaultMaxCollectionSize,
});
}
3 changes: 3 additions & 0 deletions dart/packages/fory/lib/src/deserialization_context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* under the License.
*/

import 'package:fory/src/config/fory_config.dart';
import 'package:fory/src/deserialization_dispatcher.dart';
import 'package:fory/src/meta/spec_wraps/type_spec_wrap.dart';
import 'package:fory/src/resolver/deserialization_ref_resolver.dart';
Expand All @@ -26,6 +27,7 @@ import 'package:fory/src/runtime_context.dart';
import 'package:fory/src/collection/stack.dart';

final class DeserializationContext extends Pack {
final ForyConfig config;
final HeaderBrief header;

final DeserializationDispatcher deserializationDispatcher;
Expand All @@ -38,6 +40,7 @@ final class DeserializationContext extends Pack {
const DeserializationContext(
super.structHashResolver,
super.getTagByDartType,
this.config,
this.header,
this.deserializationDispatcher,
this.refResolver,
Expand Down
1 change: 1 addition & 0 deletions dart/packages/fory/lib/src/deserialization_dispatcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class DeserializationDispatcher {
DeserializationContext deserializationContext = DeserializationContext(
StructHashResolver.inst,
typeResolver.getRegisteredTag,
conf,
header,
this,
DeserializationRefResolver.getOne(conf.ref),
Expand Down
11 changes: 11 additions & 0 deletions dart/packages/fory/lib/src/exception/fory_exception.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,14 @@ abstract class ForyException extends Error {
return buf.toString();
}
}

class InvalidDataException extends ForyException {
final String message;

InvalidDataException(this.message);

@override
void giveExceptionMessage(StringBuffer buf) {
buf.write(message);
}
}
4 changes: 4 additions & 0 deletions dart/packages/fory/lib/src/fory_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,17 @@ final class Fory {
bool basicTypesRefIgnored = true,
bool timeRefIgnored = true,
bool stringRefIgnored = false,
int maxBinarySize = ForyConfig.defaultMaxBinarySize,
int maxCollectionSize = ForyConfig.defaultMaxCollectionSize,
}) : this.fromConfig(
ForyConfig(
compatible: compatible,
ref: ref,
basicTypesRefIgnored: basicTypesRefIgnored,
timeRefIgnored: timeRefIgnored,
stringRefIgnored: stringRefIgnored,
maxBinarySize: maxBinarySize,
maxCollectionSize: maxCollectionSize,
),
);

Expand Down
7 changes: 7 additions & 0 deletions dart/packages/fory/lib/src/serializer/array_serializer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
*/

import 'dart:typed_data';
import 'package:fory/src/const/types.dart';
import 'package:fory/src/deserialization_context.dart';
import 'package:fory/src/exception/fory_exception.dart';
import 'package:fory/src/memory/byte_reader.dart';
import 'package:fory/src/memory/byte_writer.dart';
import 'package:fory/src/serialization_context.dart';
Expand Down Expand Up @@ -60,6 +62,11 @@ abstract base class NumericArraySerializer<T extends num>
@override
TypedDataList<T> read(ByteReader br, int refId, DeserializationContext pack) {
int numBytes = br.readVarUint32Small7();
if (objType == ObjType.BINARY && numBytes > pack.config.maxBinarySize) {
throw InvalidDataException(
'Binary size $numBytes exceeds maxBinarySize ${pack.config.maxBinarySize}. '
'The input data may be malicious, or need to increase the maxBinarySize when creating Fory.');
}
int length = numBytes ~/ bytesPerNum;
if (isLittleEndian || bytesPerNum == 1) {
// Fast path: direct memory copy on little-endian or for single-byte types
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import 'package:fory/src/const/ref_flag.dart';
import 'package:fory/src/const/types.dart';
import 'package:fory/src/deserialization_context.dart';
import 'package:fory/src/exception/fory_exception.dart';
import 'package:fory/src/memory/byte_reader.dart';
import 'package:fory/src/meta/spec_wraps/type_spec_wrap.dart';
import 'package:fory/src/serializer/collection/iterable_serializer.dart';
Expand All @@ -33,6 +34,11 @@ abstract base class ListSerializer extends IterableSerializer {
@override
List read(ByteReader br, int refId, DeserializationContext pack) {
int num = br.readVarUint32Small7();
if (num > pack.config.maxCollectionSize) {
throw InvalidDataException(
'List size $num exceeds maxCollectionSize ${pack.config.maxCollectionSize}. '
'The input data may be malicious, or need to increase the maxCollectionSize when creating Fory.');
}
TypeSpecWrap? elemWrap = pack.typeWrapStack.peek?.param0;
List list = newList(
num,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/

import 'package:fory/src/deserialization_context.dart';
import 'package:fory/src/exception/fory_exception.dart';
import 'package:fory/src/meta/spec_wraps/type_spec_wrap.dart';
import 'package:fory/src/const/types.dart';
import 'package:fory/src/memory/byte_reader.dart';
Expand Down Expand Up @@ -51,6 +52,11 @@ abstract base class MapSerializer<T extends Map<Object?, Object?>>
@override
T read(ByteReader br, int refId, DeserializationContext pack) {
int remaining = br.readVarUint32Small7();
if (remaining > pack.config.maxCollectionSize) {
throw InvalidDataException(
'Map size $remaining exceeds maxCollectionSize ${pack.config.maxCollectionSize}. '
'The input data may be malicious, or need to increase the maxCollectionSize when creating Fory.');
}
T map = newMap(remaining);
if (writeRef) {
pack.refResolver.setRefTheLatestId(map);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ library;
import 'package:fory/src/const/ref_flag.dart';
import 'package:fory/src/const/types.dart';
import 'package:fory/src/deserialization_context.dart';
import 'package:fory/src/exception/fory_exception.dart';
import 'package:fory/src/memory/byte_reader.dart';
import 'package:fory/src/meta/spec_wraps/type_spec_wrap.dart';
import 'package:fory/src/serializer/collection/iterable_serializer.dart';
Expand All @@ -41,6 +42,11 @@ abstract base class SetSerializer extends IterableSerializer {
@override
Set read(ByteReader br, int refId, DeserializationContext pack) {
int num = br.readVarUint32Small7();
if (num > pack.config.maxCollectionSize) {
throw InvalidDataException(
'Set size $num exceeds maxCollectionSize ${pack.config.maxCollectionSize}. '
'The input data may be malicious, or need to increase the maxCollectionSize when creating Fory.');
}
TypeSpecWrap? elemWrap = pack.typeWrapStack.peek?.param0;
Set set = newSet(
elemWrap == null || elemWrap.nullable,
Expand Down
Loading