Skip to content

[fix][sec] Upgrade ZooKeeper to 3.9.7 to address several CVEs - #27343

Merged
nodece merged 2 commits into
apache:masterfrom
lhotari:lh-fix-zookeeper-3.9.7-cves
Nov 4, 2026
Merged

nodece merged 2 commits into
apache:masterfrom
lhotari:lh-fix-zookeeper-3.9.7-cves

Conversation

@lhotari

@lhotari lhotari commented Nov 3, 2026

Copy link
Copy Markdown
Member

Motivation

Apache ZooKeeper 3.9.7 fixes these CVEs, which were announced on the ZooKeeper user list and affect the 3.9.6 version that Pulsar currently uses:

CVE CVSS 4.0 Issue
CVE-2026-100300 8.7 Unauthenticated, persistent denial of service through quota nodes
CVE-2026-115448 8.7 Unbounded recursion in snapshot serialization
CVE-2026-100252 8.6 SASL authorization bypass on the client port
CVE-2026-100302 7.1 Unbounded memory allocation in multiRead handling
CVE-2026-100235 6.0 setACL race that lets operations commit after an ACL revocation

The fix for CVE-2026-100302 adds two server-side limits, and a multiRead request over either limit fails as a whole with BadArguments:

  • multiRead.maxOps, 1000 by default
  • multiRead.maxResponseBytes, 64 MB by default

This matters to Pulsar because ZKMetadataStore sends its read batches as multiRead requests. It splits a failed batch into individual operations only after CONNECTIONLOSS, so all the reads in a rejected batch fail.

Pulsar's defaults stay within both limits:

  • metadataStoreBatchingMaxOperations is 1000, which is exactly multiRead.maxOps.
  • bin/pulsar sets jute.maxbuffer to 10 MB. A client can't receive a response larger than that, and the batching strategy aims for half of it.

Deployments need a matching ZooKeeper setting only if they raise metadataStoreBatchingMaxOperations above 1000, or raise jute.maxbuffer to around 64 MB or more. Both limits apply on the ZooKeeper server, so this also applies to ensembles that aren't started from Pulsar's configuration.

Modifications

  • Upgrade ZooKeeper from 3.9.6 to 3.9.7 and update distribution/server/src/assemble/LICENSE.bin.txt.
  • Add the two multiRead settings as commented-out defaults, with guidance on when to change them, to conf/zookeeper.conf and conf/global_zookeeper.conf.
  • Document how metadataStoreBatchingMaxOperations relates to multiRead.maxOps in conf/broker.conf, conf/standalone.conf and ServiceConfiguration.

The classes copied from ZooKeeper under jetty-upgrade/ need no changes, because their upstream sources are identical in 3.9.6 and 3.9.7. ZooKeeper 3.9.7 also moves to Netty 4.1.138 and Jackson 2.22.3, but Pulsar's resolved versions stay at Netty 4.2.20.Final and Jackson 2.21.8.

Verifying this change

  • Make sure that the change passes the CI checks.

This change is already covered by existing tests, such as MetadataStoreBatchingTest, ZKSessionTest, ZKMetadataStoreBatchIOperationTest and the tests of pulsar-zookeeper-prometheus-metrics, which pass on 3.9.7.

MetadataStoreTest has one failure with test retries disabled, testGetChildren for the ZooKeeper provider. It fails the same way on 3.9.6, so this upgrade doesn't cause it.

A temporary test against a real ZooKeeper 3.9.7 server, not included in this PR, confirmed the multiRead.maxOps behavior:

multiRead.maxOps Batch size Result
1000 (default) 1000 All reads succeed
1000 (default) 1001 All reads fail with BadArguments
1500 (parsed by QuorumPeerConfig like a setting in zookeeper.conf) 1500 All reads succeed
1500 (parsed by QuorumPeerConfig like a setting in zookeeper.conf) 1501 All reads fail

The multiRead.maxResponseBytes limit wasn't exercised by a test.

Does this pull request potentially affect one of the following parts:

If the box was checked, please highlight the changes

  • Dependencies (add or upgrade a dependency)
  • The public API
  • The schema
  • The default values of configurations
  • The threading model
  • The binary protocol
  • The REST endpoints
  • The admin CLI options
  • The metrics
  • Anything that affects deployment

ZooKeeper servers now enforce the multiRead limits described above, and deployments with raised batching settings need a matching ZooKeeper setting.

ZooKeeper 3.9.7 fixes CVE-2026-100235, CVE-2026-100252, CVE-2026-100300,
CVE-2026-100302 and CVE-2026-115448.

The fix for CVE-2026-100302 adds the server-side limits multiRead.maxOps
and multiRead.maxResponseBytes. Pulsar's defaults stay within them, so
document when a deployment needs to raise them.

Assisted-by: Claude Code/claude-opus-5-5
@nodece
nodece merged commit 5f8d34a into apache:master Nov 4, 2026
43 checks passed

@codelipenghui codelipenghui left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed head b561a9b072c4b711c21c5ef25f4711d6b86a203c.

The security upgrade is appropriate, but please address the reproduced metadata-read compatibility regression described inline while retaining the new server protections.

Validation: 61 existing focused batching, session/recovery, and Prometheus integration tests passed. A local embedded-server regression probe passed with only the ZooKeeper dependency reverted to 3.9.6, but failed with BadArguments on 3.9.7 both before and after that comparison. This concerns a non-default 40 MiB client packet limit; no default-10-MiB regression is claimed.

Comment thread gradle/libs.versions.toml Resolved
lhotari added a commit that referenced this pull request Nov 4, 2026
Co-authored-by: Matteo Merli <mmerli@apache.org>
(cherry picked from commit 5f8d34a)
lhotari added a commit that referenced this pull request Nov 4, 2026
Co-authored-by: Matteo Merli <mmerli@apache.org>
(cherry picked from commit 5f8d34a)
lhotari added a commit that referenced this pull request Nov 4, 2026
Co-authored-by: Matteo Merli <mmerli@apache.org>
(cherry picked from commit 5f8d34a)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants