From 25ac47d8a0ba82d81f1725293fbf93796fcbe67e Mon Sep 17 00:00:00 2001 From: Jens Geyer Date: Wed, 20 May 2026 01:14:54 +0200 Subject: [PATCH] THRIFT-6014: Add recursion depth limit to skip() in JavaScript library Client: js Aligns the browser JavaScript library skip() with the Node.js protocols (binary, compact, JSON), which already enforce a depth limit of 64. Adds a depth parameter and throws TProtocolException with DEPTH_LIMIT when depth exceeds 64. Adds a QUnit test harness covering the boundary. Co-Authored-By: Claude Sonnet 4.6 --- lib/js/src/thrift.js | 19 +++++++++----- lib/js/test/test-skip-depth.html | 42 +++++++++++++++++++++++++++++ lib/js/test/test-skip-depth.js | 45 ++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 lib/js/test/test-skip-depth.html create mode 100644 lib/js/test/test-skip-depth.js diff --git a/lib/js/src/thrift.js b/lib/js/src/thrift.js index 0967fcc993f..c775f8c2cdc 100644 --- a/lib/js/src/thrift.js +++ b/lib/js/src/thrift.js @@ -1433,7 +1433,14 @@ Thrift.Protocol.prototype = { /** * Method to arbitrarily skip over data */ - skip: function(type) { + skip: function(type, depth) { + depth = (depth || 0) + 1; + if (depth > 64) { + throw new Thrift.TProtocolException( + Thrift.TProtocolExceptionType.DEPTH_LIMIT, + 'Maximum skip depth exceeded' + ); + } var ret, i; switch (type) { case Thrift.Type.BOOL: @@ -1464,7 +1471,7 @@ Thrift.Protocol.prototype = { if (ret.ftype == Thrift.Type.STOP) { break; } - this.skip(ret.ftype); + this.skip(ret.ftype, depth); this.readFieldEnd(); } this.readStructEnd(); @@ -1478,8 +1485,8 @@ Thrift.Protocol.prototype = { this.rstack.pop(); } } - this.skip(ret.ktype); - this.skip(ret.vtype); + this.skip(ret.ktype, depth); + this.skip(ret.vtype, depth); } this.readMapEnd(); return null; @@ -1487,7 +1494,7 @@ Thrift.Protocol.prototype = { case Thrift.Type.SET: ret = this.readSetBegin(); for (i = 0; i < ret.size; i++) { - this.skip(ret.etype); + this.skip(ret.etype, depth); } this.readSetEnd(); return null; @@ -1495,7 +1502,7 @@ Thrift.Protocol.prototype = { case Thrift.Type.LIST: ret = this.readListBegin(); for (i = 0; i < ret.size; i++) { - this.skip(ret.etype); + this.skip(ret.etype, depth); } this.readListEnd(); return null; diff --git a/lib/js/test/test-skip-depth.html b/lib/js/test/test-skip-depth.html new file mode 100644 index 00000000000..fee205aa467 --- /dev/null +++ b/lib/js/test/test-skip-depth.html @@ -0,0 +1,42 @@ + + + + + + Thrift Javascript Bindings: Skip Depth Limit Test + + + + + + + + + + + + +

Thrift Javascript Bindings: Skip Depth Limit Test

+

+
+

+
+ + diff --git a/lib/js/test/test-skip-depth.js b/lib/js/test/test-skip-depth.js new file mode 100644 index 00000000000..212fc4eee13 --- /dev/null +++ b/lib/js/test/test-skip-depth.js @@ -0,0 +1,45 @@ +/* + * 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. + */ + +QUnit.module('Protocol skip depth limit'); + +QUnit.test('skip throws DEPTH_LIMIT when depth exceeds 64', function(assert) { + var transport = new Thrift.Transport('/service'); + var protocol = new Thrift.Protocol(transport); + + assert.throws( + function() { protocol.skip(Thrift.Type.STRUCT, 64); }, + function(e) { + return e instanceof Thrift.TProtocolException && + e.type === Thrift.TProtocolExceptionType.DEPTH_LIMIT; + }, + 'skip throws TProtocolException with DEPTH_LIMIT at depth 65' + ); +}); + +QUnit.test('skip does not throw below the depth limit', function(assert) { + var transport = new Thrift.Transport('/service'); + var protocol = new Thrift.Protocol(transport); + + // depth=63 → increments to 64, which is not > 64, so no throw before read + // I32 is a leaf: one readI32 call, no recursion + transport.setRecvBuffer('\x00\x00\x00\x00'); + assert.ok(protocol.skip(Thrift.Type.I32, 63) !== undefined || true, + 'skip at depth 63 does not throw'); +});