Skip to content

3.3.5

Choose a tag to compare

@Spomky Spomky released this 10 Sep 08:58
· 13 commits to 3.5.x since this release
3.3.5
e2208b2

Security release

This release fixes GHSA-jfrf-557c-963v (High). Everyone decoding CBOR that comes from outside the application should upgrade.

Five paths let a document written by an attacker cost far more than its size, or fail outside the documented InvalidArgumentException contract:

  • AMapObject::normalize() and IndefiniteLengthMapObject::normalize() were quadratic in the number of entries. A 469 kB map took 45.5 s, and the same cost was paid inside decode() itself when a map was used as a map key.
  • B — a decimal fraction (tag 4) or a big float (tag 5) with a large exponent expanded a six-byte item into kilobytes. 6000 such keys, 53.7 kB in all, exhausted a 128 MB limit inside decode(), with a fatal error no try/catch can intercept.
  • C — converting the byte string of a big number (tags 2 and 3) is a base conversion that brick/math performs in time quadratic in the length on every calculator but GMP. On an installation with neither ext-gmp nor ext-bcmath — what composer require gives by default — 504 bytes cost 1.8 s and 2 kB close to a hundred, inside decode().
  • D — a date-time (tag 0) holding a NUL byte raised a ValueError, which is an Error, so a caller guarding the parse with InvalidArgumentException never caught it.
  • E — an indefinite-length string with a chunk of the wrong type raised a RuntimeException, which the documentation reserves for a missing extension.
Before After
A, 100 000-entry map normalize() 45.5 s 0.098 s
A, same map used as a key, inside decode() 37.6 s 0.54 s
B, 6000 tag 4 keys at memory_limit=128M fatal OOM, exit 255 InvalidArgumentException in 0.009 s
C, 2 kB big number, no gmp/bcmath 99 s rejected, < 1 ms
D, E ValueError / RuntimeException InvalidArgumentException

Behaviour changes

Two documents that used to decode no longer do. Both are deliberate and both raise InvalidArgumentException, so a caller that guards the parse is unaffected:

  • a decimal fraction or a big float whose exponent exceeds 1024 in absolute value — DecimalFractionTag::MAX_ABSOLUTE_EXPONENT and BigFloatTag::MAX_ABSOLUTE_EXPONENT, previously 8192;
  • a big number wrapping more than 256 bytes, a 2048-bit integer — the new UnsignedBigIntegerTag::MAX_BYTE_LENGTH and NegativeBigIntegerTag::MAX_BYTE_LENGTH.

An indefinite-length string with an invalid chunk raises InvalidArgumentException rather than RuntimeException.

Install ext-gmp when the CBOR input is untrusted. Without it, big number conversion stays on a slow path; the new length bound keeps its cost proportionate to the document, but GMP is what removes it.

web-auth/webauthn-framework users

No action beyond upgrading. WebAuthn never reaches tags 0, 2, 3, 4 or 5, so neither new bound applies to it, and it catches Throwable. Verified by decoding the 25 attestation objects in the webauthn-framework fixtures — the whole object and the embedded COSE public key — against 3.3.4 and 3.3.5: identical results. The one measurable effect is in its favour: the normalize() AttestationObjectLoader performs on the whole attestation object before any validation is no longer quadratic.


Release Notes for 3.3.5

3.3.5

  • Total issues resolved: 7
  • Total pull requests resolved: 11
  • Total contributors: 1

bug