Skip to content

Commit 1175584

Browse files
Merge commit from fork
* fix: Validate input for `ArrayBuffer` parsing * fix: self-referential stack overflows * changeset * more efficiency * Merge commit from fork
1 parent e46afa6 commit 1175584

File tree

4 files changed

+61
-3
lines changed

4 files changed

+61
-3
lines changed

.changeset/slow-banks-love.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"devalue": patch
3+
---
4+
5+
fix: validate input for `ArrayBuffer` parsing

.changeset/tender-cities-check.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"devalue": patch
3+
---
4+
5+
fix: more helpful errors for inputs causing stack overflows

src/parse.js

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ export function unflatten(parsed, revivers) {
3333

3434
const hydrated = Array(values.length);
3535

36+
/**
37+
* A set of values currently being hydrated with custom revivers,
38+
* used to detect invalid cyclical dependencies
39+
* @type {Set<number> | null}
40+
*/
41+
let hydrating = null;
42+
3643
/**
3744
* @param {number} index
3845
* @returns {any}
@@ -70,7 +77,18 @@ export function unflatten(parsed, revivers) {
7077
// so we need to munge it into the format expected by a custom reviver
7178
i = values.push(value[1]) - 1;
7279
}
73-
return (hydrated[index] = reviver(hydrate(i)));
80+
81+
hydrating ??= new Set();
82+
83+
if (hydrating.has(i)) {
84+
throw new Error('Invalid circular reference');
85+
}
86+
87+
hydrating.add(i);
88+
hydrated[index] = reviver(hydrate(i));
89+
hydrating.delete(i);
90+
91+
return hydrated[index];
7492
}
7593

7694
switch (type) {
@@ -125,6 +143,12 @@ export function unflatten(parsed, revivers) {
125143
case 'Float64Array':
126144
case 'BigInt64Array':
127145
case 'BigUint64Array': {
146+
if (values[value[1]][0] !== 'ArrayBuffer') {
147+
// without this, if we receive malformed input we could
148+
// end up trying to hydrate in a circle
149+
throw new Error('Invalid data');
150+
}
151+
128152
const TypedArrayConstructor = globalThis[type];
129153
const buffer = hydrate(value[1]);
130154
if (!(buffer instanceof ArrayBuffer)) {
@@ -142,6 +166,9 @@ export function unflatten(parsed, revivers) {
142166

143167
case 'ArrayBuffer': {
144168
const base64 = value[1];
169+
if (typeof base64 !== 'string') {
170+
throw new Error('Invalid ArrayBuffer encoding');
171+
}
145172
const arraybuffer = decode64(base64);
146173
hydrated[index] = arraybuffer;
147174
break;

test/test.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,11 @@ const invalid = [
745745
json: '[["Int8Array", 1], { "length": 2 }, 1000000000]',
746746
message: 'Invalid input, expected ArrayBuffer but got object'
747747
},
748+
{
749+
name: 'ArrayBuffer with non-string value',
750+
json: '[["ArrayBuffer", { "length": 100 }]]',
751+
message: 'Invalid ArrayBuffer encoding'
752+
},
748753
{
749754
name: 'empty string',
750755
json: '',
@@ -802,13 +807,29 @@ const invalid = [
802807
name: 'bad index',
803808
json: '[{"0":1,"toString":"push"},"hello"]',
804809
message: 'Invalid input'
810+
},
811+
{
812+
name: 'TypedArray self-reference',
813+
json: '[["Uint8Array", 0]]',
814+
message: 'Invalid data'
815+
},
816+
{
817+
name: 'custom reviver self-reference',
818+
json: '[["Custom", 0]]',
819+
revivers: { Custom: (v) => v },
820+
message: 'Invalid circular reference'
821+
},
822+
{
823+
name: 'mutual TypedArray reference',
824+
json: '[["Uint8Array", 1], ["Uint8Array", 0]]',
825+
message: 'Invalid data'
805826
}
806827
];
807828

808-
for (const { name, json, message } of invalid) {
829+
for (const { name, json, message, revivers } of invalid) {
809830
uvu.test(`parse error: ${name}`, () => {
810831
assert.throws(
811-
() => parse(json),
832+
() => parse(json, revivers),
812833
(error) => {
813834
const match = error.message === message;
814835
if (!match) {

0 commit comments

Comments
 (0)