Description
Problem
If the ctx
used in DialContext
is cancelled AFTER the function returns, the returned water.Conn
will be closed.
See https://github.com/hwh33/water-context-bug for an example.
Cause
Two causes contributed to this bug risk.
WATER reuse the ctx
from DialContext
in all calls to WebAssembly-exported functions
Configured the water.Core
with the ctx
passed to DialContext
:
Lines 60 to 83 in d4faf1b
Then reused this ctx
when invoking every single WebAssembly-exported function
water/transport/v1/transport_module.go
Line 402 in d4faf1b
_init
:
water/transport/v1/transport_module.go
Line 422 in d4faf1b
_dial_fixed
(to be merged with _dial
):
water/transport/v1/transport_module.go
Line 454 in d4faf1b
_dial
:
water/transport/v1/transport_module.go
Line 486 in d4faf1b
_accept
(not with DialContext
, but would suffer similar bug risks):
water/transport/v1/transport_module.go
Line 518 in d4faf1b
_associate
(not with DialContext
, but would suffer similar bug risks):
water/transport/v1/transport_module.go
Line 548 in d4faf1b
_ctrlpipe
:
water/transport/v1/transport_module.go
Line 604 in d4faf1b
_start
:
water/transport/v1/transport_module.go
Line 612 in d4faf1b
At least some function calls, IF NOT ALL, should not be dependent on the context passed in by DialContext
.
Functions which MUST return before DialContext
can return (_init
, _dial
, _ctrlpipe
) should depend on the ctx
passed to DialContext
for no issue. However, the blocking main loop, _start
, should depend on a different context, probably context.Background()
.
WATER explicitly configured wazero
to terminate the execution when ctx
is canceled or timed out
(wazero.RuntimeConfig).WithCloseOnContextDone(true)
is invoked by default:
Lines 153 to 159 in d4faf1b
And wazero/config.go#L151-L172 noted that:
// WithCloseOnContextDone ensures the executions of functions to be terminated under one of the following circumstances:
//
// - context.Context passed to the Call method of api.Function is canceled during execution. (i.e. ctx by context.WithCancel)
// - context.Context passed to the Call method of api.Function reaches timeout during execution. (i.e. ctx by context.WithTimeout or context.WithDeadline)
// - Close or CloseWithExitCode of api.Module is explicitly called during execution.
//
// This is especially useful when one wants to run untrusted Wasm binaries since otherwise, any invocation of
// api.Function can potentially block the corresponding Goroutine forever. Moreover, it might block the
// entire underlying OS thread which runs the api.Function call. See "Why it's safe to execute runtime-generated
// machine codes against async Goroutine preemption" section in RATIONALE.md for detail.
//
// Upon the termination of the function executions, api.Module is closed.
//
// Note that this comes with a bit of extra cost when enabled. The reason is that internally this forces
// interpreter and compiler runtimes to insert the periodical checks on the conditions above. For that reason,
// this is disabled by default.
//
// See examples in context_done_example_test.go for the end-to-end demonstrations.
//
// When the invocations of api.Function are closed due to this, sys.ExitError is raised to the callers and
// the api.Module from which the functions are derived is made closed.
WithCloseOnContextDone(bool) RuntimeConfig