Skip to content

[Master] Enforce HTTP/2 maxConcurrentStreams limit on server#2637

Open
daneshk wants to merge 14 commits into
masterfrom
fix/http2-max-concurrent-streams
Open

[Master] Enforce HTTP/2 maxConcurrentStreams limit on server#2637
daneshk wants to merge 14 commits into
masterfrom
fix/http2-max-concurrent-streams

Conversation

@daneshk

@daneshk daneshk commented Jun 30, 2026

Copy link
Copy Markdown
Member

Summary

Fixes CVE-2026-47244: `DefaultHttp2Connection` in Netty prior to 4.1.135 initializes `maxActiveStreams` to `Integer.MAX_VALUE`, allowing a single TCP connection to open unbounded concurrent streams and exhaust heap memory (OOM).

  • Upgrades Netty to 4.1.135.Final which enforces the `SETTINGS_MAX_CONCURRENT_STREAMS` limit at the connection level
  • Explicitly sets `maxConcurrentStreams=100` (RFC 7540 §6.5.2 recommended default, consistent with Nginx/h2o/Go net/http2) in the server's initial SETTINGS frame via `Http2SourceConnectionHandlerBuilder`, covering all three H2 server paths: H2C upgrade, H2C prior-knowledge, and TLS+ALPN
  • Adds `http2MaxActiveStreams` as an optional field on the Ballerina `ListenerConfiguration` record (default: 100), allowing per-listener tuning without affecting the client pool
  • Set to `-1` for unlimited streams (not recommended for public-facing services)
  • Server limit is decoupled from client pool configuration — client `maxActiveStreamsPerConnection` and server `http2MaxActiveStreams` are now independent knobs

Performance

Tested with h2load (nghttp2/1.69.0) against a stock H2C listener on localhost. No regression observed — Netty 4.1.135 includes general throughput improvements alongside the security fix.

Scenario Before (Netty 4.1.133) After (Netty 4.1.135) Delta
10 streams × 10 connections 20,427 req/s 21,249 req/s +4%
50 streams × 4 connections 34,196 req/s 44,578 req/s +30%
100 streams × 10 connections 51,326 req/s 65,909 req/s +28%
200 streams × 10 connections (server caps at 100) 49,262 req/s 80,923 req/s +50%

All 200,000 requests succeeded with 0 errors in both cases. Well-behaved clients (which respect SETTINGS_MAX_CONCURRENT_STREAMS) are unaffected by the enforcement. Only clients that previously exploited the lack of enforcement (sending unlimited streams on one connection) are now correctly rate-limited.

Server SETTINGS confirmed via nghttp:
```
recv SETTINGS frame
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
```

Related Issues

This PR enforces HTTP/2 server-side concurrent stream limits across all HTTP/2 server connection flows and updates the Netty dependency to ensure the limit is applied at the connection level.

Key updates:

  • Added a new http2MaxActiveStreams listener configuration (default: 100) to let each listener tune its per-connection HTTP/2 concurrency limit; supports an “unlimited” mode via -1.
  • Plumbed the configured value through the HTTP server startup path so it applies consistently to H2C upgrade, H2C prior-knowledge, and TLS+ALPN connections.
  • Updated the HTTP/2 connection handler/builder to advertise the configured SETTINGS_MAX_CONCURRENT_STREAMS value to clients.
  • Upgraded Netty to 4.1.135.Final to align server-side stream limit enforcement with the new configuration.
  • Added/updated documentation: README files now include a brief feature mention, while detailed listener and HTTP/2 concurrency documentation was added to docs/spec/spec.md.
  • Added transport tests to verify both the configured finite limit and the unlimited configuration behavior.

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 49ee162e-3b57-46a6-b4c8-1e9baafc628b

📥 Commits

Reviewing files that changed from the base of the PR and between e71164a and 00d615c.

📒 Files selected for processing (5)
  • README.md
  • ballerina/README.md
  • docs/spec/spec.md
  • native/src/main/java/io/ballerina/stdlib/http/api/HttpUtil.java
  • native/src/main/java/io/ballerina/stdlib/http/transport/contract/config/ListenerConfiguration.java
✅ Files skipped from review due to trivial changes (2)
  • README.md
  • ballerina/README.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • native/src/main/java/io/ballerina/stdlib/http/transport/contract/config/ListenerConfiguration.java
  • native/src/main/java/io/ballerina/stdlib/http/api/HttpUtil.java

📝 Walkthrough

Walkthrough

Adds a configurable http2MaxActiveStreams setting for HTTP/2 listeners, carries it from Ballerina listener config into native transport setup and HTTP/2 pipeline initialization, and adds tests plus updated documentation.

Changes

HTTP/2 max active streams configuration

Layer / File(s) Summary
Ballerina config and docs
ballerina/http_service_endpoint.bal, README.md, ballerina/README.md
Adds the public http2MaxActiveStreams field with a default of 100 and documents the HTTP/2 stream concurrency setting in both README files.
Native config key mapping
native/src/main/java/io/ballerina/stdlib/http/api/HttpConstants.java, native/src/main/java/io/ballerina/stdlib/http/api/HttpUtil.java
Adds the HTTP endpoint config key constant and reads it from endpointConfig into the listener configuration.
Transport listener and bootstrap wiring
native/src/main/java/io/ballerina/stdlib/http/transport/contract/config/ListenerConfiguration.java, native/src/main/java/io/ballerina/stdlib/http/transport/contractimpl/DefaultHttpWsConnectorFactory.java, native/src/main/java/io/ballerina/stdlib/http/transport/contractimpl/listener/ServerConnectorBootstrap.java
Adds the transport listener field and accessors, forwards the value through server connector bootstrap, and applies it during HTTP/2 server connector creation.
HTTP/2 pipeline propagation
native/src/main/java/io/ballerina/stdlib/http/transport/contractimpl/listener/HttpServerChannelInitializer.java, native/src/main/java/io/ballerina/stdlib/http/transport/contractimpl/listener/http2/Http2SourceConnectionHandlerBuilder.java, native/src/main/java/io/ballerina/stdlib/http/transport/contractimpl/listener/http2/Http2WithPriorKnowledgeHandler.java
Threads the configured value through H2C and TLS/ALPN pipeline setup, including the prior-knowledge handler and source connection handler builder initial settings.
Transport test coverage
native/src/test/java/io/ballerina/stdlib/http/transport/http2/Http2MaxConcurrentStreamsTestCase.java, native/src/test/resources/testng.xml
Adds a transport test class that captures advertised HTTP/2 SETTINGS maxConcurrentStreams for finite and unlimited configurations, and registers it in the HTTP/2 test suite.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant HttpUtil
  participant ListenerConfiguration
  participant ServerConnectorBootstrap
  participant HttpServerChannelInitializer
  participant Http2SourceConnectionHandlerBuilder

  HttpUtil->>ListenerConfiguration: setHttp2MaxConcurrentStreams(value)
  ServerConnectorBootstrap->>HttpServerChannelInitializer: setHttp2MaxConcurrentStreams(http2MaxActiveStreams)
  HttpServerChannelInitializer->>Http2SourceConnectionHandlerBuilder: pass maxActiveStreams
  Http2SourceConnectionHandlerBuilder->>Http2SourceConnectionHandlerBuilder: initialSettings().maxConcurrentStreams(maxActiveStreams)
Loading

Possibly related PRs

Suggested reviewers: lnash94, shafreenAnfar

Poem

A rabbit hops on streams just right,
One hundred lanes feel snug and light,
Tune the knob, and off we go,
More streams to dance, more flow to show,
Hop, hop—HTTP/2 shines bright 🐇

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is informative, but it misses the required Purpose/Examples/Checklist sections and doesn't fill the template. Rewrite it to follow the template with Purpose, Examples, and the full checklist, including links or notes for each required item.
Docstring Coverage ⚠️ Warning Docstring coverage is 12.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: enforcing HTTP/2 maxConcurrentStreams on the server.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/http2-max-concurrent-streams

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@daneshk daneshk changed the title [Master] Fix CVE-2026-47244: enforce HTTP/2 maxConcurrentStreams limit on server [Master] Enforce HTTP/2 maxConcurrentStreams limit on server Jun 30, 2026
@codecov

codecov Bot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.32%. Comparing base (8d364d4) to head (00d615c).

❌ Your project check has failed because the head coverage (79.32%) is below the target coverage (80.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff              @@
##             master    #2637      +/-   ##
============================================
+ Coverage     79.22%   79.32%   +0.09%     
- Complexity        0      915     +915     
============================================
  Files           376      441      +65     
  Lines         21636    25130    +3494     
  Branches       3382     4112     +730     
============================================
+ Hits          17142    19935    +2793     
- Misses         3546     3976     +430     
- Partials        948     1219     +271     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@daneshk daneshk marked this pull request as ready for review July 1, 2026 16:24

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
native/src/test/java/io/ballerina/stdlib/http/transport/http2/Http2MaxConcurrentStreamsTestCase.java (1)

59-148: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Test only covers the H2C prior-knowledge path.

The client here connects over plaintext TCP and builds a raw Http2ConnectionHandler without an HTTP/1.1 upgrade, so this only exercises Http2WithPriorKnowledgeHandler. Per the PR objectives, the fix is meant to apply "across all HTTP/2 server paths: H2C upgrade, H2C prior-knowledge, and TLS+ALPN," but the H2C-upgrade (Http2ServerUpgradeCodec) and TLS+ALPN (Http2PipelineConfiguratorForServer) code paths — which independently construct their own Http2SourceConnectionHandlerBuilder — remain untested for this CVE-relevant setting.

Given this is a security-fix regression test, consider adding equivalent assertions for the upgrade and ALPN negotiation paths to ensure the limit is enforced consistently everywhere it's wired.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@native/src/test/java/io/ballerina/stdlib/http/transport/http2/Http2MaxConcurrentStreamsTestCase.java`
around lines 59 - 148, The new regression test in
Http2MaxConcurrentStreamsTestCase only verifies the prior-knowledge H2C path, so
it does not cover the upgrade and TLS+ALPN server paths mentioned in the PR.
Extend the test coverage by adding equivalent assertions for the
Http2ServerUpgradeCodec flow and the Http2PipelineConfiguratorForServer / ALPN
flow, using the same maxConcurrentStreams capture helper or a shared variant.
Ensure each path exercises its own Http2SourceConnectionHandlerBuilder wiring so
the configured SETTINGS_MAX_CONCURRENT_STREAMS limit is validated consistently
across all HTTP/2 server setups.
README.md (1)

110-129: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Heading style inconsistency flagged by markdownlint.

Static analysis flags line 110's #### heading as inconsistent with the document's established heading style (expected setext).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 110 - 129, The README section heading uses an ATX
style heading that conflicts with the document’s existing setext style. Update
the HTTP/2 Stream Concurrency heading in the README so it matches the
surrounding markdown heading convention, keeping the section title and content
unchanged while converting it to the expected heading format.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@native/src/main/java/io/ballerina/stdlib/http/api/HttpUtil.java`:
- Around line 1587-1591: The http2MaxActiveStreams handling in HttpUtil silently
casts a long to int in setHttp2MaxConcurrentStreams, which can truncate or
overflow the configured stream limit. Replace the direct cast with validation
consistent with the other config paths in this method and the existing
validateConfig() helper used for maxActiveStreamsPerConnection, rejecting
out-of-range values before applying them to listenerConfiguration. Keep the fix
localized in HttpUtil around the endpointConfig lookup and
listenerConfiguration.setHttp2MaxConcurrentStreams call.

---

Nitpick comments:
In
`@native/src/test/java/io/ballerina/stdlib/http/transport/http2/Http2MaxConcurrentStreamsTestCase.java`:
- Around line 59-148: The new regression test in
Http2MaxConcurrentStreamsTestCase only verifies the prior-knowledge H2C path, so
it does not cover the upgrade and TLS+ALPN server paths mentioned in the PR.
Extend the test coverage by adding equivalent assertions for the
Http2ServerUpgradeCodec flow and the Http2PipelineConfiguratorForServer / ALPN
flow, using the same maxConcurrentStreams capture helper or a shared variant.
Ensure each path exercises its own Http2SourceConnectionHandlerBuilder wiring so
the configured SETTINGS_MAX_CONCURRENT_STREAMS limit is validated consistently
across all HTTP/2 server setups.

In `@README.md`:
- Around line 110-129: The README section heading uses an ATX style heading that
conflicts with the document’s existing setext style. Update the HTTP/2 Stream
Concurrency heading in the README so it matches the surrounding markdown heading
convention, keeping the section title and content unchanged while converting it
to the expected heading format.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5da35c71-224b-4740-82c6-cf4b0de23332

📥 Commits

Reviewing files that changed from the base of the PR and between 8d364d4 and 98c70d5.

📒 Files selected for processing (13)
  • README.md
  • ballerina/README.md
  • ballerina/http_service_endpoint.bal
  • native/src/main/java/io/ballerina/stdlib/http/api/HttpConstants.java
  • native/src/main/java/io/ballerina/stdlib/http/api/HttpUtil.java
  • native/src/main/java/io/ballerina/stdlib/http/transport/contract/config/ListenerConfiguration.java
  • native/src/main/java/io/ballerina/stdlib/http/transport/contractimpl/DefaultHttpWsConnectorFactory.java
  • native/src/main/java/io/ballerina/stdlib/http/transport/contractimpl/listener/HttpServerChannelInitializer.java
  • native/src/main/java/io/ballerina/stdlib/http/transport/contractimpl/listener/ServerConnectorBootstrap.java
  • native/src/main/java/io/ballerina/stdlib/http/transport/contractimpl/listener/http2/Http2SourceConnectionHandlerBuilder.java
  • native/src/main/java/io/ballerina/stdlib/http/transport/contractimpl/listener/http2/Http2WithPriorKnowledgeHandler.java
  • native/src/test/java/io/ballerina/stdlib/http/transport/http2/Http2MaxConcurrentStreamsTestCase.java
  • native/src/test/resources/testng.xml

Comment thread native/src/main/java/io/ballerina/stdlib/http/api/HttpUtil.java Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
native/src/main/java/io/ballerina/stdlib/http/transport/contract/config/ListenerConfiguration.java (1)

289-296: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Field/accessor naming mismatch.

Field is http2MaxActiveStreams but the getter/setter are named getHttp2MaxConcurrentStreams/setHttp2MaxConcurrentStreams. Both names are used in the codebase (http2MaxActiveStreams in Ballerina config, MaxConcurrentStreams in native accessors), which is confusing when tracing the value across layers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@native/src/main/java/io/ballerina/stdlib/http/transport/contract/config/ListenerConfiguration.java`
around lines 289 - 296, The HTTP/2 stream limit accessors in
ListenerConfiguration are named differently from the backing field, which makes
the value hard to trace across layers. Align the naming by either renaming the
field to match getHttp2MaxConcurrentStreams/setHttp2MaxConcurrentStreams or
renaming the accessors to match http2MaxActiveStreams, and update all call sites
consistently so the config name and native accessor name no longer diverge.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@native/src/main/java/io/ballerina/stdlib/http/transport/contract/config/ListenerConfiguration.java`:
- Line 67: `ListenerConfiguration` is still allowing `http2MaxActiveStreams` to
stay at 0, which `DefaultHttpWsConnectorFactory` passes through for HTTP/2
listeners. Restore a Java-side fallback of 100 for this field in
`ListenerConfiguration` (or validate and reject HTTP/2 configs when the value is
unset) so direct callers that only set `version = HTTP_2_0` do not advertise
zero concurrent streams.

---

Nitpick comments:
In
`@native/src/main/java/io/ballerina/stdlib/http/transport/contract/config/ListenerConfiguration.java`:
- Around line 289-296: The HTTP/2 stream limit accessors in
ListenerConfiguration are named differently from the backing field, which makes
the value hard to trace across layers. Align the naming by either renaming the
field to match getHttp2MaxConcurrentStreams/setHttp2MaxConcurrentStreams or
renaming the accessors to match http2MaxActiveStreams, and update all call sites
consistently so the config name and native accessor name no longer diverge.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 36fd7e37-4349-46d1-b8d2-f8899f9a91e7

📥 Commits

Reviewing files that changed from the base of the PR and between 98c70d5 and e71164a.

📒 Files selected for processing (3)
  • ballerina/http_service_endpoint.bal
  • native/src/main/java/io/ballerina/stdlib/http/api/HttpUtil.java
  • native/src/main/java/io/ballerina/stdlib/http/transport/contract/config/ListenerConfiguration.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • native/src/main/java/io/ballerina/stdlib/http/api/HttpUtil.java
  • ballerina/http_service_endpoint.bal

Comment thread README.md Outdated
Comment thread ballerina/README.md Outdated
Per review feedback, the detailed HTTP/2 stream concurrency explanation
(RFC citation, defaults, tuning example) is a spec-level detail and
doesn't belong in the READMEs. Document it properly under Listener in
docs/spec/spec.md instead, and fold a brief mention into the existing
feature-highlight sentence in both READMEs to keep them consistent
with the terse style used for the other Listener/Service features.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@daneshk

daneshk commented Jul 6, 2026

Copy link
Copy Markdown
Member Author

Addressed in 00d615c: moved the HTTP/2 stream concurrency documentation out of both READMEs into docs/spec/spec.md (new section 2.1.4, plus the http2MaxActiveStreams field added to the ListenerConfiguration record listing). Both READMEs now just fold a brief mention into the existing feature-highlight sentence, consistent with how the other Listener/Service features are described there.

@sonarqubecloud

sonarqubecloud Bot commented Jul 6, 2026

Copy link
Copy Markdown

@TharmiganK TharmiganK 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.

LGTM

Comment thread docs/spec/spec.md
* 2.1.1. [Automatically starting the service](#211-automatically-starting-the-service)
* 2.1.2. [Programmatically starting the service](#212-programmatically-starting-the-service)
* 2.1.3. [Default listener](#213-default-listener)
* 2.1.4. [HTTP2 stream concurrency](#214-http2-stream-concurrency)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

should we keep the terms consistent, with HTTP/2?

Comment thread docs/spec/spec.md
password = "ballerina"
```

#### 2.1.4. HTTP2 stream concurrency

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Here also, should it be HTTP/2?

listenerConfiguration.setPipeliningEnabled(true); //Pipelining is enabled all the time
listenerConfiguration.setHttp2InitialWindowSize(endpointConfig
.getIntValue(ENDPOINT_CONFIG_HTTP2_INITIAL_WINDOW_SIZE).intValue());
listenerConfiguration.setHttp2MaxConcurrentStreams(

@YasanPunch YasanPunch Jul 7, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

In the pr description, it says: "Set to -1 for unlimited streams (not recommended for public-facing services)"

Where is this logic handled?


long maxActiveStreamsPerConnection =
poolRecord.getIntValue(HttpConstants.CONNECTION_POOLING_MAX_ACTIVE_STREAMS_PER_CONNECTION);
poolConfiguration.setHttp2MaxActiveStreamsPerConnection(

@YasanPunch YasanPunch Jul 7, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

From PR description: Set to -1 for unlimited streams (not recommended for public-facing services)

, why is it handled here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants