Skip to content

Commit 52b885e

Browse files
committed
fix decode.string() boundary checking
1 parent 4812e2e commit 52b885e

File tree

3 files changed

+60
-2
lines changed

3 files changed

+60
-2
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@colyseus/schema",
3-
"version": "3.0.65",
3+
"version": "3.0.66",
44
"description": "Binary state serializer with delta encoding for games",
55
"bin": {
66
"schema-codegen": "bin/schema-codegen",

src/encoding/decode.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ const _uint64 = new BigUint64Array(_convoBuffer);
4343
const _int64 = new BigInt64Array(_convoBuffer);
4444

4545
function utf8Read(bytes: BufferLike, it: Iterator, length: number) {
46+
// boundary check
47+
if (length > bytes.length - it.offset) { length = bytes.length - it.offset; }
48+
4649
var string = '', chr = 0;
4750
for (var i = it.offset, end = it.offset + length; i < end; i++) {
4851
var byte = bytes[i];
@@ -79,9 +82,11 @@ function utf8Read(bytes: BufferLike, it: Iterator, length: number) {
7982
continue;
8083
}
8184

82-
console.error('Invalid byte ' + byte.toString(16));
8385
// (do not throw error to avoid server/client from crashing due to hack attemps)
8486
// throw new Error('Invalid byte ' + byte.toString(16));
87+
88+
console.error('decode.utf8Read(): Invalid byte ' + byte + ' at offset ' + i + '. Skip to end of string: ' + (it.offset + length));
89+
break;
8590
}
8691
it.offset += length;
8792
return string;

test/Decoder.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import * as assert from "assert";
2+
import { decode } from "../src/encoding/decode";
3+
4+
describe("Decoder", () => {
5+
it("should handle string with length way higher than actual provided bytes", () => {
6+
// Buffer with prefix 0xdb (219) indicating str32 format
7+
// Next 4 bytes (59, 171, 66, 72) decode to string length: 1,212,328,763
8+
// But the actual buffer is only 256 bytes
9+
const buffer = [
10+
219, 59, 171, 66, 72, 65, 58, 222, 247, 182, 250, 94, 163, 168, 62, 64,
11+
134, 169, 128, 52, 237, 237, 78, 3, 92, 0, 240, 209, 213, 115, 79, 120,
12+
11, 12, 243, 204, 100, 159, 106, 13, 164, 29, 192, 154, 57, 231, 78, 14,
13+
2, 30, 99, 107, 92, 39, 5, 74, 2, 120, 10, 11, 96, 123, 49, 134,
14+
154, 222, 216, 63, 242, 125, 85, 235, 108, 56, 251, 235, 190, 63, 250, 21,
15+
240, 17, 247, 175, 180, 215, 27, 229, 116, 92, 82, 70, 84, 252, 193, 104,
16+
138, 140, 90, 86, 30, 79, 91, 77, 88, 181, 206, 224, 48, 223, 84, 37,
17+
215, 91, 109, 107, 238, 33, 116, 79, 151, 202, 14, 65, 126, 179, 172, 19,
18+
162, 120, 59, 34, 115, 46, 0, 67, 199, 224, 216, 125, 247, 59, 245, 89,
19+
153, 61, 146, 19, 165, 202, 212, 221, 56, 199, 134, 186, 181, 234, 192, 103,
20+
99, 92, 49, 66, 63, 4, 135, 97, 171, 71, 82, 249, 176, 75, 159, 198,
21+
253, 126, 119, 112, 138, 147, 18, 222, 98, 90, 112, 67, 35, 128, 136, 102,
22+
232, 75, 226, 41, 78, 117, 179, 200, 234, 224, 220, 64, 220, 110, 245, 19,
23+
16, 243, 245, 133, 141, 98, 209, 86, 65, 110, 217, 141, 221, 174, 19, 206,
24+
58, 11, 63, 18, 175, 68, 39, 218, 80, 226, 242, 2, 180, 36, 75, 40,
25+
204, 25, 195, 150, 42, 196, 63, 22, 62, 118, 43, 54, 106, 205, 194, 4
26+
];
27+
28+
const it = { offset: 0 };
29+
30+
// Verify stringCheck identifies this as a string prefix
31+
assert.strictEqual(decode.stringCheck(buffer, it), true, "stringCheck should return true for 0xdb prefix");
32+
33+
// Verify the prefix is 0xdb (219) - str32 format
34+
assert.strictEqual(buffer[0], 0xdb, "First byte should be 0xdb (219)");
35+
36+
// Read the declared string length (will be 1,212,328,763)
37+
const prefixOffset = { offset: it.offset + 1 };
38+
const declaredLength = decode.uint32(buffer, prefixOffset);
39+
assert.strictEqual(declaredLength, 1212328763, "Declared string length should be 1,212,328,763");
40+
assert.strictEqual(buffer.length, 256, "Actual buffer size should be 256 bytes");
41+
42+
// This edge case shows that malicious or corrupted data could cause:
43+
// - Memory exhaustion (trying to allocate 1.2GB+ for string concatenation)
44+
// - Potential DoS attack vector
45+
// - Reading beyond buffer boundaries
46+
let str: string;
47+
assert.doesNotThrow(() => str = decode.string(buffer, it));
48+
49+
assert.strictEqual(str.length, 3, "String length should be 3 bytes");
50+
assert.strictEqual(it.offset, 256, "Iterator offset should be 256");
51+
});
52+
});
53+

0 commit comments

Comments
 (0)