alexcrichton opened issue #14380:
To the extent that
wasi:filesystem's semantics are largely downstream of POSIX's, this setup:$ mkdir -p /tmp/t/dir && ln -s dir /tmp/t/sym_dirshows this result on native:
$ python3 -c "import os, stat; print(stat.S_ISDIR(os.lstat('/tmp/t/sym_dir/').st_mode))" Truewhereas this wasm program:
(module (import "wasi_snapshot_preview1" "path_filestat_get" (func $path_filestat_get (param i32 i32 i32 i32 i32) (result i32))) (import "wasi_snapshot_preview1" "proc_exit" (func $proc_exit (param i32))) (memory (export "memory") 1) (data (i32.const 100) "sym_dir/") (func (export "_start") (local $errno i32) (local.set $errno (call $path_filestat_get (i32.const 3) ;; fd: first preopen (i32.const 0) ;; path_flags: symlink_follow NOT set (i32.const 100) ;; path (i32.const 8) ;; path_len (i32.const 256) ;; filestat out )) (if (i32.ne (local.get $errno) (i32.const 0)) (then (call $proc_exit (local.get $errno)))) ;; exit with `filetype` (offset 16 of `filestat`): 3 = directory (call $proc_exit (i32.load8_u (i32.const 272))) ) )yields
$ wasmtime run --dir /tmp/t::/ repro.wat; echo $? 3 # Linux >= 5.6: openat2 fast path, correct $ ./no_openat2 wasmtime run --dir /tmp/t::/ repro.wat; echo $? 54 # everywhere else: ENOTDIRwhere
no_openat2is this program:<details>
#include <errno.h> #include <linux/audit.h> #include <linux/filter.h> #include <linux/seccomp.h> #include <stddef.h> #include <stdio.h> #include <sys/prctl.h> #include <sys/syscall.h> #include <unistd.h> #ifndef __NR_openat2 #define __NR_openat2 437 #endif int main(int argc, char **argv) { struct sock_filter filter[] = { /* load syscall number */ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)), /* if nr == __NR_openat2 -> return ENOSYS */ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_openat2, 0, 1), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | (ENOSYS & SECCOMP_RET_DATA)), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), }; struct sock_fprog prog = { .len = (unsigned short)(sizeof(filter) / sizeof(filter[0])), .filter = filter, }; if (argc < 2) { fprintf(stderr, "usage: %s <command> [args...]\n", argv[0]); return 2; } if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0) { perror("PR_SET_NO_NEW_PRIVS"); return 2; } if (syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER, 0, &prog) != 0) { perror("seccomp"); return 2; } execvp(argv[1], &argv[1]); perror("execvp"); return 2; }</details>
Effectively wasmtime isn't stat-ing this path correctly with the same behavior as native when using the
manually::openpath infilesystem::primitives. An LLM writeup, if useful, is here
alexcrichton added the wasi:impl label to Issue #14380.
rvolosatovs closed issue #14380:
To the extent that
wasi:filesystem's semantics are largely downstream of POSIX's, this setup:$ mkdir -p /tmp/t/dir && ln -s dir /tmp/t/sym_dirshows this result on native:
$ python3 -c "import os, stat; print(stat.S_ISDIR(os.lstat('/tmp/t/sym_dir/').st_mode))" Truewhereas this wasm program:
(module (import "wasi_snapshot_preview1" "path_filestat_get" (func $path_filestat_get (param i32 i32 i32 i32 i32) (result i32))) (import "wasi_snapshot_preview1" "proc_exit" (func $proc_exit (param i32))) (memory (export "memory") 1) (data (i32.const 100) "sym_dir/") (func (export "_start") (local $errno i32) (local.set $errno (call $path_filestat_get (i32.const 3) ;; fd: first preopen (i32.const 0) ;; path_flags: symlink_follow NOT set (i32.const 100) ;; path (i32.const 8) ;; path_len (i32.const 256) ;; filestat out )) (if (i32.ne (local.get $errno) (i32.const 0)) (then (call $proc_exit (local.get $errno)))) ;; exit with `filetype` (offset 16 of `filestat`): 3 = directory (call $proc_exit (i32.load8_u (i32.const 272))) ) )yields
$ wasmtime run --dir /tmp/t::/ repro.wat; echo $? 3 # Linux >= 5.6: openat2 fast path, correct $ ./no_openat2 wasmtime run --dir /tmp/t::/ repro.wat; echo $? 54 # everywhere else: ENOTDIRwhere
no_openat2is this program:<details>
#include <errno.h> #include <linux/audit.h> #include <linux/filter.h> #include <linux/seccomp.h> #include <stddef.h> #include <stdio.h> #include <sys/prctl.h> #include <sys/syscall.h> #include <unistd.h> #ifndef __NR_openat2 #define __NR_openat2 437 #endif int main(int argc, char **argv) { struct sock_filter filter[] = { /* load syscall number */ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)), /* if nr == __NR_openat2 -> return ENOSYS */ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_openat2, 0, 1), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | (ENOSYS & SECCOMP_RET_DATA)), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), }; struct sock_fprog prog = { .len = (unsigned short)(sizeof(filter) / sizeof(filter[0])), .filter = filter, }; if (argc < 2) { fprintf(stderr, "usage: %s <command> [args...]\n", argv[0]); return 2; } if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0) { perror("PR_SET_NO_NEW_PRIVS"); return 2; } if (syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER, 0, &prog) != 0) { perror("seccomp"); return 2; } execvp(argv[1], &argv[1]); perror("execvp"); return 2; }</details>
Effectively wasmtime isn't stat-ing this path correctly with the same behavior as native when using the
manually::openpath infilesystem::primitives. An LLM writeup, if useful, is here
Last updated: Oct 11 2026 at 04:10 UTC