Skip to content

PLEX-3289: pollOneoff fix -- prevent memory amplification - #2299

Merged
cedric-cordenier merged 2 commits into
mainfrom
PLEX-3289-poll-oneoff-fix
Jul 31, 2026
Merged

PLEX-3289: pollOneoff fix -- prevent memory amplification#2299
cedric-cordenier merged 2 commits into
mainfrom
PLEX-3289-poll-oneoff-fix

Conversation

@cedric-cordenier

Copy link
Copy Markdown
Contributor

Requires

Supports

Copilot AI review requested due to automatic review settings July 31, 2026 11:31
@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

✅ API Diff Results - github.com/smartcontractkit/chainlink-common

✅ Compatible Changes (2)

pkg/settings/cresettings.Schema (1)
  • WASMPollOneoffSubscriptionLimit — ➕ Added
pkg/workflows/wasm/host.ModuleConfig (1)
  • MaxSubscriptionsLimiter — ➕ Added

📄 View full apidiff report

Copilot AI 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.

Pull request overview

This PR introduces a configurable upper bound for the WASI poll_oneoff host call’s nsubscriptions argument to prevent guest-controlled memory amplification during subscription/event buffer handling in the WASM host runtime.

Changes:

  • Add MaxSubscriptionsLimiter to ModuleConfig and default it from a new cresettings value.
  • Enforce the subscription limit in both the legacy-DAG WASI host implementation and the execution runtime implementation.
  • Thread a context.Context into the legacy-DAG WASI linker so the limiter can be evaluated with the correct execution context.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
pkg/workflows/wasm/host/wasip1.go Wrap legacy-DAG poll_oneoff with a limiter-driven bound and plumb context.Context into the DAG linker.
pkg/workflows/wasm/host/module.go Add MaxSubscriptionsLimiter to module configuration, initialize default limiter, and pass context into linking.
pkg/workflows/wasm/host/execution.go Replace the previous nsubscriptions bound with a limiter-based bound in execution.pollOneoff.
pkg/settings/cresettings/settings.go Add WASMPollOneoffSubscriptionLimit to settings schema and defaults.
pkg/settings/cresettings/README.md Document the new setting in the settings flowchart.
pkg/settings/cresettings/defaults.toml Provide default value for WASMPollOneoffSubscriptionLimit.
pkg/settings/cresettings/defaults.json Provide default value for WASMPollOneoffSubscriptionLimit.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/workflows/wasm/host/module.go Outdated
Comment thread pkg/workflows/wasm/host/wasip1.go Outdated
Comment thread pkg/workflows/wasm/host/wasip1.go
Comment thread pkg/workflows/wasm/host/execution.go Outdated
Copilot AI review requested due to automatic review settings July 31, 2026 13:42

Copilot AI 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.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Suppressed comments (4)

pkg/workflows/wasm/host/execution.go:365

  • nsubscriptions > int32(maxSubscriptions) risks int32 overflow if the configured limit exceeds MaxInt32 (the cast can wrap). Since nsubscriptions is already bounded to a safe range, prefer comparing with int(nsubscriptions) against the int limit.
	if nsubscriptions <= 0 ||
		nsubscriptions > int32(maxSubscriptions) ||
		nsubscriptions > max(math.MaxInt32/subscriptionLen, math.MaxInt32/eventsLen) {
		return ErrnoInval

pkg/workflows/wasm/host/wasip1.go:150

  • The comparison nsubscriptions > int32(maxSubscriptions) can overflow if the configured limit exceeds MaxInt32, causing the cast to wrap and potentially rejecting valid requests unexpectedly. Since nsubscriptions is already bounded to a safe int32 range, compare by converting nsubscriptions to int instead.
		if nsubscriptions <= 0 ||
			nsubscriptions > int32(maxSubscriptions) ||
			nsubscriptions > max(math.MaxInt32/subscriptionLen, math.MaxInt32/eventsLen) {
			return ErrnoInval

pkg/workflows/wasm/host/wasip1.go:147

  • The new MaxSubscriptionsLimiter guard is a key safety control for preventing poll_oneoff memory amplification; it should be covered by a test that asserts (1) values above the limit return ErrnoInval and (2) the call does not allocate/write events for out-of-range inputs.

This issue also appears on line 147 of the same file.

		maxSubscriptions, err := cfg.MaxSubscriptionsLimiter.Limit(ctx)
		if err != nil {
			return ErrnoInval
		}
		if nsubscriptions <= 0 ||

pkg/workflows/wasm/host/execution.go:362

  • The new MaxSubscriptionsLimiter enforcement in pollOneoff should have regression coverage to ensure oversized nsubscriptions is rejected (ErrnoInval) and to guard against reintroducing large guest-controlled allocations.

This issue also appears on line 362 of the same file.

	maxSubscriptions, err := e.module.cfg.MaxSubscriptionsLimiter.Limit(e.ctx)
	if err != nil {
		return ErrnoInval
	}
	if nsubscriptions <= 0 ||

@cedric-cordenier
cedric-cordenier marked this pull request as ready for review July 31, 2026 13:49
@cedric-cordenier
cedric-cordenier requested review from a team as code owners July 31, 2026 13:49
Comment thread pkg/workflows/wasm/host/wasip1.go
@cedric-cordenier
cedric-cordenier force-pushed the PLEX-3289-poll-oneoff-fix branch from f0308a3 to 28919fe Compare July 31, 2026 14:00
Copilot AI review requested due to automatic review settings July 31, 2026 14:00

@github-actions github-actions Bot 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.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 2.

Benchmark suite Current: 4a6d855 Previous: 874889e Ratio
BenchmarkKeystore_Sign/nop/in-process 691.3 ns/op 285.2 ns/op 2.42

This comment was automatically generated by workflow using github-action-benchmark.

@cedric-cordenier
cedric-cordenier force-pushed the PLEX-3289-poll-oneoff-fix branch from 28919fe to c14023a Compare July 31, 2026 14:03

Copilot AI 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.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Copilot AI review requested due to automatic review settings July 31, 2026 14:04

Copilot AI 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.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Suppressed comments (2)

pkg/workflows/wasm/host/wasip1.go:189

  • The clock subscription parsing comment says the flag is at bytes 24-32, but it’s being decoded as a uint16. Tightening the slice and correcting the comment avoids confusion and matches the (u16 + padding) layout. (Consider mirroring the same change in execution.pollOneoff for consistency.)
				// - 24-32: flag
				newTimeout := binary.LittleEndian.Uint64(argBuf[8:16])
				flag := binary.LittleEndian.Uint16(argBuf[24:32])

pkg/workflows/wasm/host/poll_oneoff_regression_test.go:19

  • The comment has a duplicated word ("for for"), which reads like a typo.
// This means for for every subscription created in the host, we'd allocate 1.67 times

Copilot AI review requested due to automatic review settings July 31, 2026 14:09
@cedric-cordenier
cedric-cordenier force-pushed the PLEX-3289-poll-oneoff-fix branch from c14023a to 2e957d1 Compare July 31, 2026 14:09
@cedric-cordenier
cedric-cordenier force-pushed the PLEX-3289-poll-oneoff-fix branch from 2e957d1 to 4a6d855 Compare July 31, 2026 14:10

Copilot AI 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.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Suppressed comments (2)

pkg/workflows/wasm/host/poll_oneoff_regression_test.go:19

  • Spelling/grammar: the comment has a duplicated "for" ("means for for every").
// This means for for every subscription created in the host, we'd allocate 1.67 times

pkg/workflows/wasm/host/wasip1.go:166

  • The subscription argument buffer is currently sliced to the end of the subs backing array, which makes argBuf include bytes from subsequent subscriptions. While the current fixed offsets happen to work, this is brittle and makes it easier to accidentally read across subscription boundaries if the parsing logic changes. Slice argBuf to the current subscription’s range instead.
			eventType := subs[inOffset+8]
			argBuf := subs[inOffset+8+8:]

Copilot AI review requested due to automatic review settings July 31, 2026 14:13

Copilot AI 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.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Suppressed comments (2)

pkg/workflows/wasm/host/poll_oneoff_regression_test.go:20

  • The comment has a duplicated word (“for for”), which reads like a typo.
// copies the subscriptions into Go's heap and allocates a proportional buffer for events.
// This means for for every subscription created in the host, we'd allocate 1.67 times
// that much memory on the host. We fix this by rejecting calls with large nsubscriptions.

pkg/settings/cresettings/settings.go:365

  • The Schema comment says the Go runtime “never needs more than one”, but the default is set to 128. This is confusing as written; either the default should be lowered to 1, or the comment should explain why the default allows more than one (e.g., headroom for non-Go guests / future runtime changes).
	// WASMPollOneoffSubscriptionLimit bounds nsubscriptions in the WASI
	// poll_oneoff host call. Checked against the Go wasip1 runtime
	// (https://cs.opensource.google/go/go/+/refs/tags/go1.26.2:src/runtime/netpoll_wasip1.go),
	// which never needs more than one.
	WASMPollOneoffSubscriptionLimit Setting[int] `unit:"{subscription}"`

@cedric-cordenier
cedric-cordenier added this pull request to the merge queue Jul 31, 2026
Merged via the queue into main with commit 91f54f9 Jul 31, 2026
30 of 32 checks passed
@cedric-cordenier
cedric-cordenier deleted the PLEX-3289-poll-oneoff-fix branch July 31, 2026 14:40
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.

4 participants