Skip to content

Conversation

t4chib4ne
Copy link

@t4chib4ne t4chib4ne commented Sep 10, 2025

Adds WASIp2 component support for wasmtime by checking whether the given WebAssembly is a module or a component and then calling the appropriate functions.
The calls are nothing fancy just a very basic setup giving the WebAssembly component full access to the WASIp2 implementation of wasmtime.

Closes #1871

Still a Draft because of:

  • WAT to WASM needs to be tested for components
  • Need for a WASIp2 equivalent of wasi_config_preopen_dir ⇒ calling this function for a WASIp2 config also works
  • Checking whether WASIp2 components have some sort of default export ⇒ calling wasi:cli/[email protected]#run should be fine as wasmtime will call the highest version available (e.g. wasi:cli/[email protected]#run)

Summary by Sourcery

Enable WASIp2 component support in wasmtime by detecting if the input is a module or component and invoking the appropriate execution path with dynamically loaded Wasmtime APIs and WASIp2 configuration

New Features:

  • Support execution of WASIp2 WebAssembly components in wasmtime

Enhancements:

  • Detect whether a WebAssembly file is a module or component and dispatch to the respective execution path
  • Extract module and component execution logic into separate libwasmtime_run_module and libwasmtime_run_component functions
  • Dynamically load additional Wasmtime C API symbols required for component instantiation and execution
  • Configure WASIp2 for components with inherited standard I/O and argument forwarding

Documentation:

  • Update wasm-wasi-example to use ENTRYPOINT instead of CMD

Summary by Sourcery

Enable WASIp2 component support in the wasmtime handler by detecting input type, refactoring module logic, and adding a separate execution path for components with dynamic API loading and enhanced WASI configuration

New Features:

  • Add WASI preview2 (WASIp2) component execution support in the wasmtime handler
  • Detect and dispatch between WebAssembly modules and components at runtime

Enhancements:

  • Refactor module execution into libwasmtime_run_module and introduce libwasmtime_run_component for component handling
  • Dynamically load Wasmtime C API symbols required for component instantiation and execution
  • Enhance WASI configuration to specify directory and file permissions when preopening

Documentation:

  • Update wasm-wasi-example to use ENTRYPOINT instead of CMD

Copy link

sourcery-ai bot commented Sep 10, 2025

Reviewer's Guide

Add WASIp2 component support to wasmtime by detecting whether the input is a module or a component, refactoring execution into separate runners, dynamically loading the necessary Wasmtime C API symbols, and updating example docs.

File-Level Changes

Change Details Files
Detect and dispatch between module and component execution
  • Compile .wat to wasm on the fly
  • Validate binary with wasmtime_module_validate
  • Route to libwasmtime_run_module or libwasmtime_run_component
src/libcrun/handlers/wasmtime.c
Extract module execution into a dedicated function
  • Move existing module logic into libwasmtime_run_module
  • Resolve symbols and handle errors via dlsym
  • Configure WASI and invoke module run path
src/libcrun/handlers/wasmtime.c
Implement WASIp2 component runner
  • Load component & WASIp2 symbols dynamically
  • Instantiate component and configure WASIp2 with inherited I/O and args
  • Link WASIp2 interface and call the run function
src/libcrun/handlers/wasmtime.c
Extend WASI preopen API and perms
  • Update wasi_config_preopen_dir signature with dir/file perms
  • Apply read/write perms in both module and component paths
src/libcrun/handlers/wasmtime.c
Update example documentation
  • Change CMD to ENTRYPOINT in wasm-wasi-example
docs/wasm-wasi-example.md

Assessment against linked issues

Issue Objective Addressed Explanation
#1871 Add support for running WASIp2 WebAssembly components via crun with wasmtime.
#1871 Detect whether the input WebAssembly file is a module (WASIp1) or a component (WASIp2) and dispatch to the appropriate execution path.
#1871 Update documentation to reflect correct usage for running WASM images (e.g., using ENTRYPOINT instead of CMD).

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@t4chib4ne
Copy link
Author

So I wanted to implement the wasi_config_preopen_dir for WASIp2 by first trying to write something to the filesystem with WASIp1 which resulted in a permission denied error.
The reason is that in the current code wasi_config_preopen_dir is called without its arguments to set the permissions for the dir so the module does not have any permissions.

Is this by choice or a bug?

@giuseppe
Copy link
Member

@flouthoc PTAL

Copy link
Member

@giuseppe giuseppe left a comment

Choose a reason for hiding this comment

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

some nits, also could you use wasmtime: $DESCRIPTION for your commits instead of [wasmtime], otherwise LGTM

@t4chib4ne
Copy link
Author

Thanks for taking a look, hope I didn't oversee anything!

The only thing keeping this PR as a draft is that I would like to give both WASM modules and components the ability to read & write to the working directory. But the previous implementation did not allow this.

Are there any security concerns or can this feature simply be added?


wasm_byte_vec_t wasm;
// Load and parse container entrypoint
FILE *file = fopen (pathname, "rbe");
Copy link
Collaborator

Choose a reason for hiding this comment

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

What is e in rbe here ?

Copy link
Author

Choose a reason for hiding this comment

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

This part of the code was just copied. The e seems to be a glibc extension which sets the O_CLOEXEC flag on the descriptor. This flag causes the fd to be automatically closed upon calling one of the exec functions.

A few lines later the fd is actually closed by hand and none the exec functions are visibly being called. As e is not standards compliant maybe it should be removed?

Sources: fopen(3) and open(2) man pages

Copy link
Collaborator

Choose a reason for hiding this comment

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

As e is not standards compliant maybe it should be removed?

Yes if it's not necessary I think we should remove it, wdyt @giuseppe

Copy link
Author

Choose a reason for hiding this comment

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

Some extra info: Closing the fd manually later does not prevent all issues O_CLOEXEC tries to solve. The glibc man page states that it is a glibc extension so I figured it would not be standards compliant but musl and FreeBSDs libc actually also implement the e in fopen.

Sources: musl and FreeBSDs libc

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we can remove it if it is not required, I see similar comment later from review bot #1877 (comment)

Copy link
Author

Choose a reason for hiding this comment

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

After reading other parts of the code for quite some time, I think it is safe to say e is not required. This is also encouraged by the fact that right before the fopen we are always in a single threaded context thus the mentioned leak in open(2) is not possible.

FROM scratch
COPY hello.wasm /
CMD ["/hello.wasm"]
ENTRYPOINT ["/hello.wasm"]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you write it in commit message or here, about why is this change needed ?

Copy link
Author

Choose a reason for hiding this comment

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

Oh, sorry forgot to mention it ...

After adding the pass through of argv to the component I wanted to try it out. Upon running podman run mywasm-image:latest arg1 arg2 podman would replace CMD (the wasm binary) with arg1 which of course does not work. Changing to ENTRYPOINT resolves this.

Copy link
Collaborator

@flouthoc flouthoc Sep 12, 2025

Choose a reason for hiding this comment

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

This looks like a breaking change. I think adding this message to commit logs can help us revisit this and fix this if needed.

cc @giuseppe

Copy link
Author

Choose a reason for hiding this comment

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

Adding this to the commit logs should be possible but I don't really see where this is a breaking change? Without the changes of this PR a wasm module runs into the same problem. IIRC this is also the same behavior a non-wasm container would show.

Copy link
Author

Choose a reason for hiding this comment

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

Is the message in ce7d656 OK?

…sm-wasi-example

In docs/wasm-wasi-example.md the Containerfile had the
WebAssembly binary in the `CMD` instruction. But running
the container via podman like so

`podman run mywasm-image:latest arg1 arg2`

does not work as podman instructs crun to run `arg1 arg2`
instead of `hello.wasm arg1 arg2` resulting in the error
`arg1: command not found`.
Using `ENTRYPOINT` in the Containerfile makes the previously
mentioned podman command work.

Signed-off-by: Maximilian Hüter <[email protected]>
Signed-off-by: Maximilian Hüter <[email protected]>
Signed-off-by: Maximilian Hüter <[email protected]>
Signed-off-by: Maximilian Hüter <[email protected]>
@t4chib4ne t4chib4ne marked this pull request as ready for review September 19, 2025 09:55
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • Consider extracting the repeated dlsym() lookups into a helper utility that takes an array of symbol names and returns function pointers, reducing duplicate code.
  • Relying on matching the literal error string “component passed to module validation” for component detection is brittle; consider using a dedicated component detection API or inspecting the Wasm magic/header instead.
  • When a symbol lookup fails, include the specific missing symbol name in the error output to make debugging dynamic loading issues easier.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider extracting the repeated dlsym() lookups into a helper utility that takes an array of symbol names and returns function pointers, reducing duplicate code.
- Relying on matching the literal error string “component passed to module validation” for component detection is brittle; consider using a dedicated component detection API or inspecting the Wasm magic/header instead.
- When a symbol lookup fails, include the specific missing symbol name in the error output to make debugging dynamic loading issues easier.

## Individual Comments

### Comment 1
<location> `src/libcrun/handlers/wasmtime.c:91` </location>
<code_context>
+
+  wasm_byte_vec_t wasm;
+  // Load and parse container entrypoint
+  FILE *file = fopen (pathname, "rbe");
+  if (! file)
+    error (EXIT_FAILURE, 0, "error loading entrypoint");
</code_context>

<issue_to_address>
**issue:** The use of 'rbe' as the fopen mode is non-standard and may cause portability issues.

Use 'rb' for reading binary files unless 'rbe' is required and confirmed to be supported across all target platforms.
</issue_to_address>

### Comment 2
<location> `src/libcrun/handlers/wasmtime.c:107` </location>
<code_context>
+  // If entrypoint contains a webassembly text format
+  // compile it on the fly and convert to equivalent
+  // binary format.
+  if (has_suffix (pathname, "wat") > 0)
+    {
+      wasmtime_error_t *err = wasmtime_wat2wasm ((char *) wasm.data, file_size, &wasm_bytes);
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Suffix check for 'wat' may match unintended filenames.

This check may incorrectly match filenames like 'mywat' or 'format'. Use '.wat' as the suffix to ensure only intended files are processed.

```suggestion
  if (has_suffix (pathname, ".wat") > 0)
```
</issue_to_address>

### Comment 3
<location> `src/libcrun/handlers/wasmtime.c:129` </location>
<code_context>
+      wasmtime_error_message (err, &error_message);
+      wasmtime_error_delete (err);
+
+      if (strcmp ((char *) error_message.data, "component passed to module validation") != 0)
+        error (EXIT_FAILURE, 0, "failed to validate module: %.*s", (int) error_message.size, error_message.data);
+
</code_context>

<issue_to_address>
**suggestion (bug_risk):** String comparison for error message is brittle and may break with upstream changes.

Consider using an error code or a more stable identifier instead of matching the error message string, to avoid breakage from upstream changes.

Suggested implementation:

```c
      wasmtime_error_message (err, &error_message);

      // Use a more robust check for the error type.
      // If wasmtime_error_as_exit_status is available, use it.
      int exit_status = 0;
      bool is_component_error = false;
#ifdef WASMTIME_HAS_ERROR_EXIT_STATUS
      if (wasmtime_error_as_exit_status(err, &exit_status)) {
        // Check for a specific exit status if known for component error.
        if (exit_status == WASMTIME_COMPONENT_VALIDATION_ERROR_CODE) {
          is_component_error = true;
        }
      }
#endif
      // Fallback: check for substring in error message.
      if (!is_component_error) {
        if (memmem(error_message.data, error_message.size, "component passed to module validation", strlen("component passed to module validation")) == NULL)
          error (EXIT_FAILURE, 0, "failed to validate module: %.*s", (int) error_message.size, error_message.data);
      }

      wasmtime_error_delete (err);

```

- You may need to define `WASMTIME_HAS_ERROR_EXIT_STATUS` and `WASMTIME_COMPONENT_VALIDATION_ERROR_CODE` if the wasmtime API provides such error codes. If not, you can remove the conditional and rely on the substring check.
- If you have a helper function for error type checking, use that instead of the inline logic.
- Make sure to include the necessary headers for `memmem` if not already present.
</issue_to_address>

### Comment 4
<location> `src/libcrun/handlers/wasmtime.c:278` </location>
<code_context>
   wasi_config_inherit_stdout (wasi_config);
   wasi_config_inherit_stderr (wasi_config);
-  wasi_config_preopen_dir (wasi_config, ".", ".");
+  wasi_config_preopen_dir (
+      wasi_config,
+      ".",
</code_context>

<issue_to_address>
**🚨 suggestion (security):** Hardcoded permissions for preopened directory may be overly permissive.

Evaluate if both read and write permissions are required, or if access can be restricted to enhance security.

Suggested implementation:

```c
      WASMTIME_WASI_DIR_PERMS_READ,
      WASMTIME_WASI_FILE_PERMS_READ);

```

If your application requires write access, you can revert to the original permissions. Otherwise, this change will restrict access to read-only, improving security.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Signed-off-by: Maximilian Hüter <[email protected]>
Signed-off-by: Maximilian Hüter <[email protected]>
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.

Running WASIp2 Components with crun+wasmtime supported?
3 participants