Skip to content

Fail when a declared guest resource destructor cannot be resolved #9

Description

@JairusSW

Problem

Guest-defined resource destructors are registered through a lazy resolver, but resolution failure is silently treated as “no destructor”:

func resourceDtor(resolve func() api.Function) func(context.Context, uint32) error {
    return func(ctx context.Context, rep uint32) error {
        fn := resolve()
        if fn == nil {
            return nil
        }
        ...
    }
}

resolveDefinedResourceDtors likewise describes unresolved destructors as best-effort and deliberately skips them rather than failing instantiation/drop.

Relevant code:

Impact

A resource type that declares a destructor has observable cleanup semantics. If its function index is malformed, validation must reject the component. If the function is valid but unavailable because the graph has not instantiated its defining module yet, dropping the resource cannot be treated as successful—the representation is removed while required guest cleanup never runs.

This can leak guest-managed state, skip security-sensitive cleanup, and make a malformed or incorrectly ordered graph appear to execute successfully.

Suggested fix

  • Validate every ResourceDesc.Dtor index and core function signature during component validation/graph construction.
  • Preserve lazy lookup only for initialization ordering, but return a trap/error when a declared destructor is still unavailable at drop time.
  • Do not remove the handle and report success as though the resource had no destructor.
  • Make the unavailable-before-module-instantiation case explicit: either graph ordering guarantees availability before the first possible drop, or an attempted early drop traps deterministically.

Regression tests

  • An out-of-range destructor function index must reject the component before instantiation mutates runtime state.
  • A valid destructor increments observable state exactly once on drop.
  • A destructor unavailable during an early core start must trap/fail instantiation, never silently succeed.
  • A trapping destructor must poison the instance and must not be rerun by a later drop/close.
  • Nested resource transfer must still invoke the defining component's destructor.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions