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 whether the input is a module or component, dynamically loading required Wasmtime API symbols, and dispatching to separate execution paths with appropriate WASI/WASIp2 configuration.

New Features:

  • Support execution of WASIp2 WebAssembly components in Wasmtime

Enhancements:

  • Interpret binary header to distinguish modules from components and dispatch accordingly
  • Extract module and component execution into separate libwasmtime_run_module and libwasmtime_run_component functions
  • Dynamically load additional Wasmtime C API symbols at runtime
  • Automatically inherit standard I/O, environment, arguments, and preopen the current directory
  • Improve error handling and exit status propagation for both modules and components

Documentation:

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

@sourcery-ai
Copy link

sourcery-ai bot commented Sep 10, 2025

Reviewer's Guide

Refactor the wasmtime handler to dynamically load Wasmtime C API symbols, abstract VM initialization, detect WebAssembly encoding, and route execution to either a module or WASIp2 component runner.

File-Level Changes

Change Details Files
Refactor dynamic symbol loading and VM setup
  • Introduce libwasmtime_load_symbol for unified dlsym resolution
  • Define WASMTIME_COMMON_SYMBOLS macro to load common API symbols
  • Add struct libwasmtime_vm and libwasmtime_setup_vm/libwasmtime_free_vm for engine/store/context management
src/libcrun/handlers/wasmtime.c
Add WebAssembly encoding detection and dispatching
  • Implement wasm_interpret_header to distinguish modules vs. components
  • Branch in libwasmtime_exec to invoke module or component runner based on detected encoding
src/libcrun/handlers/wasmtime.c
src/libcrun/handlers/handler-utils.c
src/libcrun/handlers/handler-utils.h
Extract module execution into libwasmtime_run_module
  • Move module compilation, WASI linking, and default export invocation into separate runner
  • Use wasmtime_error_exit_status and trap handling for clean exit codes
src/libcrun/handlers/wasmtime.c
Implement WASIp2 component support via libwasmtime_run_component
  • Compile and instantiate components with wasmtime_component APIs
  • Link WASIp2 and retrieve the wasi:cli/[email protected]#run function
  • Handle post-return calls and extract exit status from component execution
src/libcrun/handlers/wasmtime.c
Update Containerfile example to use ENTRYPOINT
  • Replace CMD with ENTRYPOINT in wasm-wasi-example.md
docs/wasm-wasi-example.md

Assessment against linked issues

Issue Objective Addressed Explanation
#1871 Enable crun to detect and execute WASIp2 WebAssembly components using wasmtime, in addition to WASIp1 modules.
#1871 Update documentation (wasm-wasi-example.md) to reflect correct usage for WASIp2 components and ENTRYPOINT.

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.

Copy link
Author

Choose a reason for hiding this comment

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

I just found this commit 0f0d5be which adds e to a lot of fopen calls. Also in wasmtime.
Even though I said removing the flag should be fine I am not 100% sure. Thus I would like to add the e flag back

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]>
@t4chib4ne t4chib4ne marked this pull request as draft October 2, 2025 09:47
@t4chib4ne
Copy link
Author

t4chib4ne commented Oct 2, 2025

I marked the PR as a draft again because I don't want to trigger CI all the time. There is also a bigger change in the works which cleans up all of the dlsym calls into three macros to reduce repetition. Part of it has also accidentally been committed to 3777fba which I'll cleanup soon.

Edit: Seems like CI still get triggered ... and it's now just one macro.

@TomSweeneyRedHat
Copy link
Member

@t4chib4ne the tests are not happy. @giuseppe is that expected?

@t4chib4ne
Copy link
Author

@t4chib4ne the tests are not happy. @giuseppe is that expected?

I saw that but they failed because of a failed provisioning of the test environment due to an outage.
Either I just can't find the button or I am not allowed to manually trigger the tests.

@giuseppe
Copy link
Member

giuseppe commented Nov 5, 2025

@flouthoc PTAL

Copy link
Collaborator

@flouthoc flouthoc left a comment

Choose a reason for hiding this comment

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

LGTM

@flouthoc
Copy link
Collaborator

flouthoc commented Nov 5, 2025

@t4chib4ne I think some functions need change, how did you build WASIp2 I built and installed wasmtime c-api headers from https://github.com/bytecodealliance/wasmtime

My build failed with

src/libcrun/handlers/wasmtime.c: In function 'libwasmtime_run_component':
src/libcrun/handlers/wasmtime.c:327:3: error: unknown type name 'wasmtime_wasip2_config_t'
  327 |   wasmtime_wasip2_config_t *(*wasmtime_wasip2_config_new) (void)
      |   ^~~~~~~~~~~~~~~~~~~~~~~~
src/libcrun/handlers/wasmtime.c:329:49: error: unknown type name 'wasmtime_wasip2_config_t'
  329 |   void (*wasmtime_wasip2_config_inherit_stdin) (wasmtime_wasip2_config_t *config)
      |                                                 ^~~~~~~~~~~~~~~~~~~~~~~~
src/libcrun/handlers/wasmtime.c:331:50: error: unknown type name 'wasmtime_wasip2_config_t'
  331 |   void (*wasmtime_wasip2_config_inherit_stdout) (wasmtime_wasip2_config_t *config)
      |                                                  ^~~~~~~~~~~~~~~~~~~~~~~~
src/libcrun/handlers/wasmtime.c:333:50: error: unknown type name 'wasmtime_wasip2_config_t'
  333 |   void (*wasmtime_wasip2_config_inherit_stderr) (wasmtime_wasip2_config_t *config)
      |                                                  ^~~~~~~~~~~~~~~~~~~~~~~~
src/libcrun/handlers/wasmtime.c:335:39: error: unknown type name 'wasmtime_wasip2_config_t'
  335 |   void (*wasmtime_wasip2_config_arg) (wasmtime_wasip2_config_t *config, const char *arg, size_t arg_len)
      |                                       ^~~~~~~~~~~~~~~~~~~~~~~~
src/libcrun/handlers/wasmtime.c:337:69: error: unknown type name 'wasmtime_wasip2_config_t'
  337 | *wasmtime_context_set_wasip2) (wasmtime_context_t *context, wasmtime_wasip2_config_t *config)
      |                                                             ^~~~~~~~~~~~~~~~~~~~~~~~

src/libcrun/handlers/wasmtime.c:397:3: error: unknown type name 'wasmtime_wasip2_config_t'
  397 |   wasmtime_wasip2_config_t *wasi_config = wasmtime_wasip2_config_new ();
      |   ^~~~~~~~~~~~~~~~~~~~~~~~
src/libcrun/handlers/wasmtime.c:402:3: error: implicit declaration of function 'wasmtime_wasip2_config_inherit_stdin'; did you mean 'wasmtime_wasip2_config_new'? [-Wimplicit-function-declaration]
  402 |   wasmtime_wasip2_config_inherit_stdin (wasi_config);
      |   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      |   wasmtime_wasip2_config_new
src/libcrun/handlers/wasmtime.c:403:3: error: implicit declaration of function 'wasmtime_wasip2_config_inherit_stdout'; did you mean 'wasi_config_inherit_stdout'? [-Wimplicit-function-declaration]
  403 |   wasmtime_wasip2_config_inherit_stdout (wasi_config);
      |   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      |   wasi_config_inherit_stdout
src/libcrun/handlers/wasmtime.c:404:3: error: implicit declaration of function 'wasmtime_wasip2_config_inherit_stderr'; did you mean 'wasi_config_inherit_stderr'? [-Wimplicit-function-declaration]
  404 |   wasmtime_wasip2_config_inherit_stderr (wasi_config);
      |   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      |   wasi_config_inherit_stderr
src/libcrun/handlers/wasmtime.c:413:5: error: implicit declaration of function 'wasmtime_wasip2_config_arg'; did you mean 'wasmtime_wasip2_config_new'? [-Wimplicit-function-declaration]
  413 |     wasmtime_wasip2_config_arg (wasi_config, *arg, strlen (*arg));
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~
      |     wasmtime_wasip2_config_new
src/libcrun/handlers/wasmtime.c:415:3: error: implicit declaration of function 'wasmtime_context_set_wasip2'; did you mean 'wasmtime_context_set_wasi'? [-Wimplicit-function-declaration]
  415 |   wasmtime_context_set_wasip2 (context, wasi_config);
      |   ^~~~~~~~~~~~~~~~~~~~~~~~~~~
      |   wasmtime_context_set_wasi
make[2]: *** [Makefile:2030: src/libcrun/handlers/libcrun_la-wasmtime.lo] Error 1
make[2]: Leaving directory '/home/ar/work/crun'
make[1]: *** [Makefile:2806: all-recursive] Error 1
make[1]: Leaving directory '/home/ar/work/crun'
make: *** [Makefile:1130: all] Error 2

@t4chib4ne
Copy link
Author

t4chib4ne commented Nov 5, 2025

I wrote an ebuild (Gentoo) and installed it that way. It just downloads a release from the repo you linked, configures -DBUILD_SHARED_LIBS=yes and -DWASMTIME_FASTEST_RUNTIME=yes, adds a SONAME to the lib via a patch and then installs it. (the sed commands in the ebuild file are there because of an ebuild specific problem)

I don't know how you installed the header files but you need to build them and use them from the cmake artifacts dir, not just copy them from the repo (After trying to manually build I think cmake also just copies from the repo). I never tried the install via cargo which is mentioned in the doxygen docs.

My libwasmtime install looks like this:
~ equery files dev-libs/wasmtime
 * Searching for wasmtime in dev-libs ...
 * Contents of dev-libs/wasmtime-37.0.2:
/usr
/usr/include
/usr/include/doc-wasm.h
/usr/include/wasi.h
/usr/include/wasm.h
/usr/include/wasm.hh
/usr/include/wasmtime
/usr/include/wasmtime.h
/usr/include/wasmtime.hh
/usr/include/wasmtime/async.h
/usr/include/wasmtime/component
/usr/include/wasmtime/component.h
/usr/include/wasmtime/component/component.h
/usr/include/wasmtime/component/func.h
/usr/include/wasmtime/component/instance.h
/usr/include/wasmtime/component/linker.h
/usr/include/wasmtime/component/val.h
/usr/include/wasmtime/conf.h
/usr/include/wasmtime/config.h
/usr/include/wasmtime/config.hh
/usr/include/wasmtime/engine.h
/usr/include/wasmtime/engine.hh
/usr/include/wasmtime/error.h
/usr/include/wasmtime/error.hh
/usr/include/wasmtime/extern.h
/usr/include/wasmtime/extern.hh
/usr/include/wasmtime/extern_declare.hh
/usr/include/wasmtime/func.h
/usr/include/wasmtime/func.hh
/usr/include/wasmtime/global.h
/usr/include/wasmtime/global.hh
/usr/include/wasmtime/instance.h
/usr/include/wasmtime/instance.hh
/usr/include/wasmtime/linker.h
/usr/include/wasmtime/linker.hh
/usr/include/wasmtime/memory.h
/usr/include/wasmtime/memory.hh
/usr/include/wasmtime/module.h
/usr/include/wasmtime/module.hh
/usr/include/wasmtime/profiling.h
/usr/include/wasmtime/sharedmemory.h
/usr/include/wasmtime/span.hh
/usr/include/wasmtime/store.h
/usr/include/wasmtime/store.hh
/usr/include/wasmtime/table.h
/usr/include/wasmtime/table.hh
/usr/include/wasmtime/trap.h
/usr/include/wasmtime/trap.hh
/usr/include/wasmtime/types
/usr/include/wasmtime/types.hh
/usr/include/wasmtime/types/export.hh
/usr/include/wasmtime/types/extern.hh
/usr/include/wasmtime/types/func.hh
/usr/include/wasmtime/types/global.hh
/usr/include/wasmtime/types/import.hh
/usr/include/wasmtime/types/memory.hh
/usr/include/wasmtime/types/table.hh
/usr/include/wasmtime/types/val.hh
/usr/include/wasmtime/val.h
/usr/include/wasmtime/val.hh
/usr/include/wasmtime/wasi.hh
/usr/include/wasmtime/wasip2.h
/usr/include/wasmtime/wat.h
/usr/include/wasmtime/wat.hh
/usr/lib64
/usr/lib64/libwasmtime.a
/usr/lib64/libwasmtime.so
/usr/share
/usr/share/doc
/usr/share/doc/wasmtime-37.0.2
/usr/share/doc/wasmtime-37.0.2/README.md.bz2

Edit:
Was able to build (not test) crun with wasmtime in a docker.io/library/debian:testing container by installing libwasmtime from pure source code release wasmtime-v37.0.2-src.tar.gz (this will install to system paths!):

cmake -S crates/c-api -B target/c-api --install-prefix /usr
cmake --build target/c-api
cmake --install target/c-api

Then build crun with:

./autogen
./configure --disable-systemd --enable-bpf --enable-caps --enable-seccomp --with-wasmtime
make install-exec

I had to install build-essential cmake rustc libprotobuf-c-dev libcap-dev libseccomp-dev libyajl-dev go-md2man autoconf python3 automake pkg-config libtool

@t4chib4ne
Copy link
Author

Sorry for spamming but I think I found the issue: wasmtime 37 and 38 still ship wasip2.h thus when building from release still compiles. When using the wasmtime main branch it won't work because wasip2.h has been removed and wasip2 functionality has been combined with the "standard" wasi.h c-api: bytecodealliance/wasmtime@cde2e04

I will add changes to this PR to use the now more "unified" c-api. If I understand the linked commit message correctly then wasip2 interoperability with wasi.h should already be available even if wasmtime 39 has not been released yet.

Thanks @flouthoc for testing the build!

@t4chib4ne t4chib4ne marked this pull request as draft November 5, 2025 22:59
Signed-off-by: Maximilian Hüter <[email protected]>
@flouthoc
Copy link
Collaborator

flouthoc commented Nov 6, 2025

Sorry for spamming but I think I found the issue: wasmtime 37 and 38 still ship wasip2.h thus when building from release still compiles. When using the wasmtime main branch it won't work because wasip2.h has been removed and wasip2 functionality has been combined with the "standard" wasi.h c-api: bytecodealliance/wasmtime@cde2e04

I will add changes to this PR to use the now more "unified" c-api. If I understand the linked commit message correctly then wasip2 interoperability with wasi.h should already be available even if wasmtime 39 has not been released yet.

Thanks @flouthoc for testing the build!

Sure please tag, when you update the PR.

@t4chib4ne t4chib4ne marked this pull request as ready for review November 7, 2025 14:32
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 and they look great!

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location> `src/libcrun/handlers/wasmtime.c:77` </location>
<code_context>
-      || wasmtime_wat2wasm == NULL)
-    error (EXIT_FAILURE, 0, "could not find symbol in `libwasmtime.so`");
+
+  if (vm == NULL)
+    vm = malloc (sizeof (struct libwasmtime_vm));
+
</code_context>

<issue_to_address>
**issue (bug_risk):** Potential memory leak if malloc fails in libwasmtime_setup_vm.

Explicitly check if malloc returns NULL and handle the error to prevent undefined behavior from dereferencing vm.
</issue_to_address>

### Comment 2
<location> `src/libcrun/handlers/wasmtime.c:251-252` </location>
<code_context>
+static void *
+libwasmtime_load_symbol (void *cookie, char *const symbol)
+{
+  void *sym = dlsym (cookie, symbol);
+  if (sym == NULL)
+    error (EXIT_FAILURE, 0, "could not find symbol in `libwasmtime.so`: %.*s", (int) strlen (symbol), symbol);
</code_context>

<issue_to_address>
**suggestion:** Error message in libwasmtime_load_symbol may print garbage for symbol name.

Since symbol is expected to be null-terminated, using %s would simplify the error message and avoid issues with non-printable or unexpected characters.

```suggestion
  if (sym == NULL)
    error (EXIT_FAILURE, 0, "could not find symbol in `libwasmtime.so`: %s", symbol);
```
</issue_to_address>

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

<issue_to_address>
**suggestion (bug_risk):** Suffix check for .wat may miss uppercase or mixed-case extensions.

The current check will not recognize files with uppercase or mixed-case ".wat" extensions. Use a case-insensitive comparison to ensure all valid WAT files are detected.
</issue_to_address>

### Comment 4
<location> `src/libcrun/handlers/wasmtime.c:203-206` </location>
<code_context>
+  if (fseek (file, 0L, SEEK_END))
+    error (EXIT_FAILURE, 0, "error fully loading entrypoint");
   size_t file_size = ftell (file);
+  if (file_size == (size_t) -1L)
+    error (EXIT_FAILURE, 0, "error getting entrypoint size");
   wasm_byte_vec_new_uninitialized (&wasm, file_size);
</code_context>

<issue_to_address>
**suggestion:** Casting -1L to size_t may be confusing and non-portable.

Comparing ftell(file) directly to -1 improves clarity and avoids potential portability issues with casting.

```suggestion
  long file_size = ftell (file);
  if (file_size == -1)
    error (EXIT_FAILURE, 0, "error getting entrypoint size");
  wasm_byte_vec_new_uninitialized (&wasm, (size_t)file_size);
```
</issue_to_address>

### Comment 5
<location> `src/libcrun/handlers/wasmtime.c:243` </location>
<code_context>
+  else
+    error (EXIT_FAILURE, 0, "unsupported wasm encoding detected");
+
+  libwasmtime_free_vm (cookie, &vm);
+  exit (status);
+}
</code_context>

<issue_to_address>
**issue (bug_risk):** libwasmtime_free_vm does not free vm struct if allocated inside libwasmtime_setup_vm.

To prevent memory leaks, ensure libwasmtime_free_vm also frees the vm struct if it was allocated with malloc in libwasmtime_setup_vm.
</issue_to_address>

### Comment 6
<location> `src/libcrun/handlers/wasmtime.c:57` </location>
<code_context>
+    void (*wasmtime_error_delete) (wasmtime_error_t * error)                             \
+        = libwasmtime_load_symbol ((cookie), "wasmtime_error_delete");
+
+static void *
+libwasmtime_load_symbol (void *cookie, char *const symbol);
+
</code_context>

<issue_to_address>
**issue (complexity):** Consider building a single API table of Wasmtime symbols at load time and passing it via the cookie to simplify symbol access and reduce repetitive dlsym calls.

Here’s one way to dramatically cut down all the repeated `dlsym(…, …)` calls and make the call‐sites much easier to read.  The idea is:

 1. On load, build a single “API table” (struct) that holds *all* the Wasmtime symbols you need.
 2. Store that in your `cookie` (instead of the raw `dlopen` handle).
 3. Everywhere else, just do `api->wasm_engine_new()` etc.

That way you only ever do each `dlsym()` once, and the rest of your code becomes a one‐liner instead of a multi‐line load.  

Example:

```c
/* add near top of file */
struct libwasmtime_api {
  void    *dlh;
  wasm_engine_t *(*wasm_engine_new)(void);
  void    (*wasm_engine_delete)(wasm_engine_t *);
  wasmtime_store_t *(*wasmtime_store_new)(wasm_engine_t*, void*, void(*)(void*));
  /* … all the rest of your symbols … */
};

static int libwasmtime_load(void **cookie, libcrun_error_t *err) {
  struct libwasmtime_api *api = malloc(sizeof(*api));
  if (!api) return crun_make_error(err, ENOMEM, "alloc api");
  api->dlh = dlopen("libwasmtime.so", RTLD_NOW);
  if (!api->dlh)
    return crun_make_error(err, 0, "dlopen: %s", dlerror());

  #define LOAD(sym)                                                      \
    do { api->sym = dlsym(api->dlh, #sym);                                \
         if (!api->sym)                                                  \
           error(EXIT_FAILURE, 0, "symbol %s missing in libwasmtime", #sym); \
    } while(0)

  LOAD(wasm_engine_new);
  LOAD(wasm_engine_delete);
  LOAD(wasmtime_store_new);
  /* … load the rest here … */

  #undef LOAD

  *cookie = api;
  return 0;
}
```

Then in your helpers you just grab the table:

```c
static struct libwasmtime_vm *
libwasmtime_setup_vm(void *cookie, char *const argv[], struct libwasmtime_vm *vm)
{
  struct libwasmtime_api *api = cookie;

  vm->engine = api->wasm_engine_new();
  if (!vm->engine)
    error(EXIT_FAILURE, 0, "could not create engine");

  vm->store  = api->wasmtime_store_new(vm->engine, NULL, NULL);
  if (!vm->store)
    error(EXIT_FAILURE, 0, "could not create store");

  vm->context = api->wasmtime_store_context(vm->store);
  /* … etc … */
  return vm;
}

static void
libwasmtime_free_vm(void *cookie, struct libwasmtime_vm *vm)
{
  struct libwasmtime_api *api = cookie;
  api->wasmtime_store_delete(vm->store);
  api->wasm_engine_delete(vm->engine);
}
```

And similarly in `libwasmtime_run_module()` / `run_component()` you replace each `libwasmtime_load_symbol(cookie, "...")` with `api->that_symbol(...)`.  This removes *hundreds* of repetitive loader calls, centralizes error‐checking, and makes the “real” logic in each helper much shorter.
</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.

@t4chib4ne
Copy link
Author

Should be good to review now, @flouthoc

I tested against wasmtime 37 and the release-39.0.0 branch.

@t4chib4ne t4chib4ne requested a review from flouthoc November 7, 2025 21:18
- rm `malloc` from libwasmtime_setup_vm
- check entrypoint extension case insensitive
- keep `ftell` return value as `long`

Signed-off-by: Maximilian Hüter <[email protected]>
@flouthoc
Copy link
Collaborator

Should be good to review now, @flouthoc

I tested against wasmtime 37 and the release-39.0.0 branch.

@t4chib4ne The binary builds fine but when I am testing a simple "hello world" example it fails with

podman run localhost/test-wasm:latest 
write to file `/proc/thread-self/attr/current`: Permission denied

I tried example from docs which you have updated. Could you share a small test how you are testing it ?

@t4chib4ne
Copy link
Author

Should be good to review now, @flouthoc
I tested against wasmtime 37 and the release-39.0.0 branch.

@t4chib4ne The binary builds fine but when I am testing a simple "hello world" example it fails with

podman run localhost/test-wasm:latest 
write to file `/proc/thread-self/attr/current`: Permission denied

I tried example from docs which you have updated. Could you share a small test how you are testing it ?

I basically use a subset of the docs.

  1. Compile the following rust code to wasm32-wasip1 and wasm32-wasip2.
  2. Build Container images via the buildah command and Containerfile from the docs (albeit with different tags).
  3. Run with podman run --rm -e RUST_BACKTRACE=full hello-wasm:p2 arg1 arg2.

rust code:

use std::{
    env,
    fs::File,
    io::{self, Write},
    process::exit,
};

fn main() -> std::io::Result<()> {
    println!("Hello, world from wasm!");
    for arg in env::args() {
        println!("Got arg: {}", &arg);
    }

    for (envkey, envvar) in env::vars() {
        println!("Got env: {} = {}", &envkey, &envvar);
    }

    eprintln!("Will try to create a file in ./ ...");

    let mut file = File::create("./hello.txt")?;
    file.write_all(b"Hello, world form wasm!")?;

    //let exit_code = 0;
    //eprintln!("Will now exit with {}", exit_code);
    //exit(exit_code);

    //Err(io::Error::new(io::ErrorKind::Other, "Oh noes!"))
    Ok(())
}

The test with wasmtime 37 is done normally and rootless. Against wasmtime 39 I build it in a privileged container and run the buildah and podman commands rootful but my user is mapped to root.

I investigated with strace and could not find any call to openat with /proc/thread-self/attr/current. The file seems to be used to get info about the LSMs on the system. I don't use any LSM.

Will setup a VM with an LSM installed and try reproducing the error.

@t4chib4ne
Copy link
Author

Sadly I cannot reproduce the issue.
The setup is a Fedora Workstation 43 VM on which I installed libwasmtime release-39.0.0 branch and crun.
The copy+paste from the docs: the rust source, compiled to wasm32-wasip2, built image with buildah and provided Containerfile, copy+paste+ran the podman command.

strace showed in the vm setup that podman opens /proc/thread-self/attr/current for reading twice and (I assume, its hard to see) crun opens it once for writing which did not fail.

@flouthoc could you please take a look with strace too?

For me running strace -s 256 -ff -o strace.log.d/strace.log podman run --rm mywasm-image:latest and then in strace.log.d using grep '/proc/thread-self/attr/current' * produces the following:

strace.log.6119:openat(AT_FDCWD, "/proc/thread-self/attr/current", O_RDONLY|O_CLOEXEC) = 3
strace.log.6143:openat(AT_FDCWD, "/proc/thread-self/attr/current", O_WRONLY|O_CLOEXEC) = 3
strace.log.6161:openat(AT_FDCWD, "/proc/thread-self/attr/current", O_RDONLY|O_CLOEXEC) = 3

Running a linux/amd64 image only produces the two reads. I have yet to find out why crun now wants to write to that file but I think it is not wasmtime itself because running with wasmedge produces the same output.

@flouthoc
Copy link
Collaborator

Sadly I cannot reproduce the issue. The setup is a Fedora Workstation 43 VM on which I installed libwasmtime release-39.0.0 branch and crun. The copy+paste from the docs: the rust source, compiled to wasm32-wasip2, built image with buildah and provided Containerfile, copy+paste+ran the podman command.

strace showed in the vm setup that podman opens /proc/thread-self/attr/current for reading twice and (I assume, its hard to see) crun opens it once for writing which did not fail.

@flouthoc could you please take a look with strace too?

For me running strace -s 256 -ff -o strace.log.d/strace.log podman run --rm mywasm-image:latest and then in strace.log.d using grep '/proc/thread-self/attr/current' * produces the following:

strace.log.6119:openat(AT_FDCWD, "/proc/thread-self/attr/current", O_RDONLY|O_CLOEXEC) = 3
strace.log.6143:openat(AT_FDCWD, "/proc/thread-self/attr/current", O_WRONLY|O_CLOEXEC) = 3
strace.log.6161:openat(AT_FDCWD, "/proc/thread-self/attr/current", O_RDONLY|O_CLOEXEC) = 3

Running a linux/amd64 image only produces the two reads. I have yet to find out why crun now wants to write to that file but I think it is not wasmtime itself because running with wasmedge produces the same output.

I will look into this today, thanks for sharing your example.

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?

4 participants