diff --git a/src/filters/misc.ts b/src/filters/misc.ts index 57ed57812..a80dd6ece 100644 --- a/src/filters/misc.ts +++ b/src/filters/misc.ts @@ -9,13 +9,19 @@ function defaultFilter (this: FilterImpl, value: T1, def return isFalsy(value, this.context) ? defaultValue : value } -function json (value: any, space = 0) { - return JSON.stringify(value, null, space) +function json (this: FilterImpl, value: any, space = 0) { + const memoryLimit = this.context.memoryLimit + return JSON.stringify(value, (_key, val) => { + memoryLimit.use(typeof val === 'string' ? val.length : 1) + return val + }, space) } -function inspect (value: any, space = 0) { +function inspect (this: FilterImpl, value: any, space = 0) { + const memoryLimit = this.context.memoryLimit const ancestors: object[] = [] return JSON.stringify(value, function (this: unknown, _key: unknown, value: any) { + memoryLimit.use(typeof value === 'string' ? value.length : 1) if (typeof value !== 'object' || value === null) return value // `this` is the object that value is contained in, i.e., its direct parent. while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) ancestors.pop() diff --git a/test/integration/liquid/dos.spec.ts b/test/integration/liquid/dos.spec.ts index a3ac56804..65d78adb5 100644 --- a/test/integration/liquid/dos.spec.ts +++ b/test/integration/liquid/dos.spec.ts @@ -114,6 +114,22 @@ describe('DoS related', function () { expect(() => liquid.parseAndRenderSync('{{ array | array_to_sentence_string }}', { array })) .toThrow('memory alloc limit exceeded') }) + it('should charge json serialization of concat-doubled arrays', () => { + const liquid = new Liquid({ memoryLimit: 1e4 }) + const src = '{%- assign a = s | split: "NOSEP" -%}' + + '{%- assign a = a | concat: a -%}{%- assign a = a | concat: a -%}{%- assign a = a | concat: a -%}' + + '{{ a | json | size }}' + expect(() => liquid.parseAndRenderSync(src, { s: 'a'.repeat(5000) })) + .toThrow('memory alloc limit exceeded') + }) + it('should charge inspect serialization of concat-doubled arrays', () => { + const liquid = new Liquid({ memoryLimit: 1e4 }) + const src = '{%- assign a = s | split: "NOSEP" -%}' + + '{%- assign a = a | concat: a -%}{%- assign a = a | concat: a -%}{%- assign a = a | concat: a -%}' + + '{{ a | inspect | size }}' + expect(() => liquid.parseAndRenderSync(src, { s: 'a'.repeat(5000) })) + .toThrow('memory alloc limit exceeded') + }) it('should charge strip_html input length to memoryLimit', () => { const liquid = new Liquid({ memoryLimit: 100 }) expect(() => liquid.parseAndRenderSync('{{ s | strip_html }}', { s: 'a'.repeat(200) }))