Skip to content

Prevents direct use of host functions via ExportedFunction #2259

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions api/wasm.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,13 @@ type Module interface {

// ExportedFunction returns a function exported from this module or nil if it wasn't.
//
// Note: The default wazero.ModuleConfig attempts to invoke `_start`, which
// in rare cases can close the module. When in doubt, check IsClosed prior
// to invoking a function export after instantiation.
// # Notes
// - The default wazero.ModuleConfig attempts to invoke `_start`, which
// in rare cases can close the module. When in doubt, check IsClosed prior
// to invoking a function export after instantiation.
// - The semantics of host functions assumes the existence of an "importing module" because, for example, the host function needs access to
// the memory of the importing module. Therefore, direct use of ExportedFunction is forbidden for host modules.
// Practically speaking, it is usually meaningless to directly call a host function from Go code as it is already somewhere in Go code.
ExportedFunction(name string) Function

// ExportedFunctionDefinitions returns all the exported function
Expand Down
17 changes: 16 additions & 1 deletion builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ type HostFunctionBuilder interface {
// are deferred until Compile.
// - Functions are indexed in order of calls to NewFunctionBuilder as
// insertion ordering is needed by ABI such as Emscripten (invoke_*).
// - The semantics of host functions assumes the existence of an "importing module" because, for example, the host function needs access to
// the memory of the importing module. Therefore, direct use of ExportedFunction is forbidden for host modules.
// Practically speaking, it is usually meaningless to directly call a host function from Go code as it is already somewhere in Go code.
type HostModuleBuilder interface {
// Note: until golang/go#5860, we can't use example tests to embed code in interface godocs.

Expand Down Expand Up @@ -341,12 +344,24 @@ func (b *hostModuleBuilder) Compile(ctx context.Context) (CompiledModule, error)
return c, nil
}

// hostModuleInstance is a wrapper around api.Module that prevents calling ExportedFunction.
type hostModuleInstance struct{ api.Module }

// ExportedFunction implements api.Module ExportedFunction.
func (h hostModuleInstance) ExportedFunction(name string) api.Function {
panic("calling ExportedFunction is forbidden on host modules. See the note on ExportedFunction interface")
}

// Instantiate implements HostModuleBuilder.Instantiate
func (b *hostModuleBuilder) Instantiate(ctx context.Context) (api.Module, error) {
if compiled, err := b.Compile(ctx); err != nil {
return nil, err
} else {
compiled.(*compiledModule).closeWithModule = true
return b.r.InstantiateModule(ctx, compiled, NewModuleConfig())
m, err := b.r.InstantiateModule(ctx, compiled, NewModuleConfig())
if err != nil {
return nil, err
}
return hostModuleInstance{m}, nil
}
}
4 changes: 4 additions & 0 deletions builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,10 @@ func TestNewHostModuleBuilder_Instantiate(t *testing.T) {
m, err := r.NewHostModuleBuilder("env").Instantiate(testCtx)
require.NoError(t, err)

// Calling ExportedFunction should fail.
err = require.CapturePanic(func() { m.ExportedFunction("any") })
require.EqualError(t, err, `calling ExportedFunction is forbidden on host modules. See the note on ExportedFunction interface`)

// If this was instantiated, it would be added to the store under the same name
require.Equal(t, r.Module("env"), m)

Expand Down
4 changes: 1 addition & 3 deletions internal/wasm/store_module_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package wasm
import (
"errors"
"fmt"

"github.com/tetratelabs/wazero/api"
)

// deleteModule makes the moduleName available for instantiation again.
Expand Down Expand Up @@ -88,7 +86,7 @@ func (s *Store) registerModule(m *ModuleInstance) error {
}

// Module implements wazero.Runtime Module
func (s *Store) Module(moduleName string) api.Module {
func (s *Store) Module(moduleName string) *ModuleInstance {
m, err := s.module(moduleName)
if err != nil {
return nil
Expand Down
8 changes: 7 additions & 1 deletion runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,13 @@ func (r *runtime) Module(moduleName string) api.Module {
if len(moduleName) == 0 {
return nil
}
return r.store.Module(moduleName)
m := r.store.Module(moduleName)
if m == nil {
return nil
} else if m.Source.IsHostModule {
return hostModuleInstance{m}
}
return m
}

// CompileModule implements Runtime.CompileModule
Expand Down
Loading