Skip to content

THRIFT-6047: Limit struct/union/exception read/write recursion depth in PHP library#3552

Open
Jens-G wants to merge 1 commit into
apache:masterfrom
Jens-G:php-recursion-depth
Open

THRIFT-6047: Limit struct/union/exception read/write recursion depth in PHP library#3552
Jens-G wants to merge 1 commit into
apache:masterfrom
Jens-G:php-recursion-depth

Conversation

@Jens-G
Copy link
Copy Markdown
Member

@Jens-G Jens-G commented May 28, 2026

THRIFT-6047: Limit struct/union/exception read/write recursion depth in the PHP library

Change

Enforces a recursion-depth counter (incrementRecursionDepth/decrementRecursionDepth on TProtocol, limit DEFAULT_RECURSION_DEPTH = 64, TProtocolException::DEPTH_LIMIT on excess) at the two mutually exclusive generated-code call sites: the generator wraps the inline read/write loop (default --gen php), and TBase::readStruct/writeStruct carry the guard for oop mode. Previously only skip() was bounded.

Also guards TException::readStruct/writeStruct. TException does not extend TBase — it carries its own copies of those methods — so oop-mode exceptions (whose generated read()/write() delegate to them) remained unbounded after the TBase change. They now increment / try / finally / decrement identically, closing the gap. (Found while adding the exception test below.)

Test

A generated-code round-trip integration test over a recursive struct (RecTree), union (RecUnion) and exception (RecError) from RecursionDepth.thrift, generated in both modes (PhpRec inline, PhpRecOop oop) and exercised over Binary, Compact and JSON:

  • a chain at exactly the limit round-trips (proving the inline and oop guards do not double-count — it would reject at 32 otherwise);
  • a chain past the limit is rejected on write;
  • a hand-serialized over-limit payload is rejected on read, crafted with the real recursive field (id 1 = list<self>) so the reader recurses through the guarded struct path rather than the separate, unbounded skip().

Validated locally (PHP 8.1, PHPUnit 10.5): 54/54 pass; phpcs (PSR-12) and phpstan clean. Without the TException guard, the oop-mode exception write/read cases fail (the gap is real). binary_inline mode (no TProtocol) stays unguarded as before.

🤖 Generated with Claude Code

Copy link
Copy Markdown
Contributor

@sveneld sveneld left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functionally solid — see two style nits inline. Will follow up on the binary_inline / integration-test items separately.

Comment thread lib/php/lib/Base/TBase.php Outdated
Comment thread lib/php/lib/Base/TBase.php Outdated
Comment thread lib/php/test/Unit/Lib/Protocol/TProtocolTest.php Outdated
Comment thread lib/php/test/Unit/Lib/Protocol/TProtocolTest.php
@Jens-G Jens-G marked this pull request as draft May 28, 2026 22:45
@Jens-G Jens-G force-pushed the php-recursion-depth branch from b8b00bc to a89e821 Compare May 31, 2026 09:32
@mergeable mergeable Bot added build and general CI cmake, automake and build system changes github_actions Pull requests that update GitHub Actions code labels May 31, 2026
@Jens-G Jens-G force-pushed the php-recursion-depth branch from a89e821 to be3d387 Compare May 31, 2026 09:40
@Jens-G Jens-G marked this pull request as ready for review May 31, 2026 09:45
@Jens-G Jens-G requested review from fishy and jimexist as code owners May 31, 2026 09:45
Comment thread lib/php/lib/Base/TBase.php
…in PHP library

Client: php

A recursion-depth counter (incrementRecursionDepth/decrementRecursionDepth,
limit DEFAULT_RECURSION_DEPTH = 64, TProtocolException::DEPTH_LIMIT on excess)
is enforced at the two mutually exclusive generated-code call sites: the
generator wraps the inline read/write loop (default --gen php), and
TBase::readStruct/writeStruct carry the guard for oop mode. Previously only
skip() was bounded.

Also guard TException::readStruct/writeStruct. TException does not extend TBase
-- it carries its own copy of those methods -- so oop-mode exceptions
(generated read()/write() delegate to them) were still unbounded after the
TBase change. They now increment/try/finally/decrement identically, so a
recursive exception is bounded in both inline and oop modes.

Replace the isolated unit tests with a generated-code round-trip integration
test over a recursive struct (RecTree), union (RecUnion) and exception
(RecError) from RecursionDepth.thrift, generated in both modes (PhpRec inline,
PhpRecOop oop) and exercised over Binary, Compact and JSON: a chain at exactly
the limit round-trips (proving the inline and oop guards do not double-count),
a chain past it is rejected on write, and a hand-serialized over-limit payload
is rejected on read (crafted with the real recursive field, id 1 = list<self>,
so the reader recurses through the guarded struct path rather than skip()).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Jens-G Jens-G force-pushed the php-recursion-depth branch from be3d387 to ac82ebe Compare June 1, 2026 18:33
@Jens-G Jens-G changed the title THRIFT-6047: Limit struct read/write recursion depth in PHP library THRIFT-6047: Limit struct/union/exception read/write recursion depth in PHP library Jun 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build and general CI cmake, automake and build system changes compiler github_actions Pull requests that update GitHub Actions code php

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants