Skip to content
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
122 changes: 122 additions & 0 deletions .github/workflows/libc-test-conformance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
name: libc-test Conformance

on:
push:
branches:
- main
paths:
- "native/wasmvm/**"
- "packages/wasmvm/**"
- "scripts/validate-libc-test-exclusions.ts"
- "scripts/generate-libc-test-report.ts"
- "scripts/import-libc-test.ts"
- "scripts/conformance-exclusion-schema.ts"
- ".github/workflows/libc-test-conformance.yml"
pull_request:
branches:
- main
paths:
- "native/wasmvm/**"
- "packages/wasmvm/**"
- "scripts/validate-libc-test-exclusions.ts"
- "scripts/generate-libc-test-report.ts"
- "scripts/import-libc-test.ts"
- "scripts/conformance-exclusion-schema.ts"
- ".github/workflows/libc-test-conformance.yml"

jobs:
libc-test-conformance:
name: libc-test Conformance (musl kernel behavior)
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

# --- Rust / WASM build ---
- name: Set up Rust toolchain
uses: dtolnay/rust-toolchain@nightly
with:
toolchain: nightly-2026-03-01
targets: wasm32-wasip1
components: rust-src

- name: Install wasm-opt (binaryen)
run: sudo apt-get update && sudo apt-get install -y binaryen

- name: Cache WASM build artifacts
uses: actions/cache@v4
with:
path: |
native/wasmvm/target
native/wasmvm/vendor
key: wasm-${{ runner.os }}-${{ hashFiles('native/wasmvm/Cargo.lock', 'native/wasmvm/rust-toolchain.toml') }}

- name: Build WASM binaries
run: cd native/wasmvm && make wasm

# --- C toolchain (wasi-sdk + patched sysroot) ---
- name: Cache wasi-sdk
id: cache-wasi-sdk
uses: actions/cache@v4
with:
path: native/wasmvm/c/vendor/wasi-sdk
key: wasi-sdk-25-${{ runner.os }}-${{ runner.arch }}

- name: Download wasi-sdk
if: steps.cache-wasi-sdk.outputs.cache-hit != 'true'
run: make -C native/wasmvm/c wasi-sdk

- name: Cache patched wasi-libc sysroot
id: cache-sysroot
uses: actions/cache@v4
with:
path: |
native/wasmvm/c/sysroot
native/wasmvm/c/vendor/wasi-libc
key: wasi-libc-sysroot-${{ runner.os }}-${{ hashFiles('native/wasmvm/patches/wasi-libc/*.patch', 'native/wasmvm/scripts/patch-wasi-libc.sh') }}

- name: Build patched wasi-libc sysroot
if: steps.cache-sysroot.outputs.cache-hit != 'true'
run: make -C native/wasmvm/c sysroot

# --- Build libc-test (WASM + native) ---
- name: Build libc-test binaries (WASM + native)
run: make -C native/wasmvm/c libc-test libc-test-native

# --- Node.js / TypeScript ---
- name: Set up pnpm
uses: pnpm/action-setup@v4
with:
version: 8.15.6

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
cache-dependency-path: pnpm-lock.yaml

- name: Install dependencies
run: pnpm install --frozen-lockfile

# --- Run conformance tests ---
- name: Run libc-test conformance tests
run: pnpm vitest run packages/wasmvm/test/libc-test-conformance.test.ts

- name: Validate exclusion list
run: pnpm tsx scripts/validate-libc-test-exclusions.ts

# --- Generate report ---
- name: Generate conformance report MDX
if: always()
run: pnpm tsx scripts/generate-libc-test-report.ts

# --- Upload artifacts ---
- name: Upload conformance report
if: always()
uses: actions/upload-artifact@v4
with:
name: libc-test-conformance-report
path: |
libc-test-conformance-report.json
docs/libc-test-conformance-report.mdx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: POSIX Conformance
name: os-test Conformance

on:
push:
Expand All @@ -7,26 +7,26 @@ on:
paths:
- "native/wasmvm/**"
- "packages/wasmvm/**"
- "scripts/validate-posix-exclusions.ts"
- "scripts/generate-posix-report.ts"
- "scripts/validate-os-test-exclusions.ts"
- "scripts/generate-os-test-report.ts"
- "scripts/import-os-test.ts"
- "scripts/posix-exclusion-schema.ts"
- ".github/workflows/posix-conformance.yml"
- "scripts/conformance-exclusion-schema.ts"
- ".github/workflows/os-test-conformance.yml"
pull_request:
branches:
- main
paths:
- "native/wasmvm/**"
- "packages/wasmvm/**"
- "scripts/validate-posix-exclusions.ts"
- "scripts/generate-posix-report.ts"
- "scripts/validate-os-test-exclusions.ts"
- "scripts/generate-os-test-report.ts"
- "scripts/import-os-test.ts"
- "scripts/posix-exclusion-schema.ts"
- ".github/workflows/posix-conformance.yml"
- "scripts/conformance-exclusion-schema.ts"
- ".github/workflows/os-test-conformance.yml"

jobs:
posix-conformance:
name: POSIX Conformance (os-test)
os-test-conformance:
name: os-test Conformance (POSIX.1-2024)
runs-on: ubuntu-latest
steps:
- name: Checkout repository
Expand Down Expand Up @@ -100,23 +100,23 @@ jobs:
run: pnpm install --frozen-lockfile

# --- Run conformance tests ---
- name: Run POSIX conformance tests
run: pnpm vitest run packages/wasmvm/test/posix-conformance.test.ts
- name: Run os-test conformance tests
run: pnpm vitest run packages/wasmvm/test/os-test-conformance.test.ts

- name: Validate exclusion list
run: pnpm tsx scripts/validate-posix-exclusions.ts
run: pnpm tsx scripts/validate-os-test-exclusions.ts

# --- Generate report ---
- name: Generate conformance report MDX
if: always()
run: pnpm tsx scripts/generate-posix-report.ts
run: pnpm tsx scripts/generate-os-test-report.ts

# --- Upload artifacts ---
- name: Upload conformance report
if: always()
uses: actions/upload-artifact@v4
with:
name: posix-conformance-report
name: os-test-conformance-report
path: |
posix-conformance-report.json
docs/posix-conformance-report.mdx
os-test-conformance-report.json
docs/os-test-conformance-report.mdx
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
### POSIX Conformance Test Integrity

- **no test-only workarounds** — if a C override fixes broken libc behavior (fcntl, realloc, strfmon, etc.), it MUST go in the patched sysroot (`native/wasmvm/patches/wasi-libc/`) so all WASM programs get the fix; never link overrides only into test binaries — that inflates conformance numbers while real users still hit the bug
- **never replace upstream test source files** — if an os-test `.c` file fails due to a platform difference (e.g. `sizeof(long)`), exclude it via `posix-exclusions.json` with the real reason; do not swap in a rewritten version that changes what the test validates
- **never replace upstream test source files** — if an os-test `.c` file fails due to a platform difference (e.g. `sizeof(long)`), exclude it via `os-test-exclusions.json` with the real reason; do not swap in a rewritten version that changes what the test validates
- **kernel behavior belongs in the kernel, not the test runner** — if a test requires runtime state (POSIX directories like `/tmp`, `/usr`, device nodes, etc.), implement it in the kernel/device-layer so all users get it; the test runner should not create kernel state that real users won't have
- **no suite-specific VFS special-casing** — the test runner must not branch on suite name to inject different filesystem state; if a test needs files to exist, either the kernel should provide them or the test should be excluded
- **categorize exclusions honestly** — if a failure is fixable with a patch or build flag, it's `implementation-gap`, not `wasm-limitation`; reserve `wasm-limitation` for things genuinely impossible in wasm32-wasip1 (no 80-bit long double, no fork, no mmap)
Expand Down Expand Up @@ -90,7 +90,7 @@
## GitHub Issues

- when fixing a bug or implementation gap tracked by a GitHub issue, close the issue in the same PR using `gh issue close <number> --comment "Fixed in <commit-hash>"`
- when removing a test from `posix-exclusions.json` because the fix landed, close the linked issue
- when removing a test from `os-test-exclusions.json` or `libc-test-exclusions.json` because the fix landed, close the linked issue
- do not leave resolved issues open — verify with `gh issue view <number>` if unsure

## Tool Integration Policy
Expand Down
85 changes: 85 additions & 0 deletions docs-internal/posix-gaps-audit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# POSIX Implementation Gaps Audit

> Adversarial review of the WasmVM POSIX implementation. Goal: identify what breaks when real software runs unmodified.
>
> os-test conformance: 3347/3350 (99.9%) — but os-test only covers C library functions, not kernel/runtime behavior.
>
> Each claim was verified by independent adversarial agents reading the actual source code.

## WILL_BREAK — Real software fails

All 11 claims verified TRUE against source code.

| # | Issue | What breaks | Where | Verified |
|---|-------|-------------|-------|----------|
| 1 | **No server sockets** (bind/listen/accept) | nginx, Express, Redis, Postgres, any daemon | wasi-ext has no bind/listen/accept imports; 0008-sockets.patch is client-only | TRUE |
| 2 | **No Unix domain sockets** (AF_UNIX) | docker.sock, ssh-agent, systemd socket activation | net_connect takes "host:port" string, no AF_UNIX path | TRUE |
| 3 | **O_EXCL not checked in fdOpen** | Atomic lock file creation (SQLite, Make, pkg managers) | kernel.ts fdOpen only handles O_CREAT; O_EXCL stored but never checked | TRUE |
| 4 | **O_TRUNC not applied in kernel fdOpen** | Shell `>` redirect, log rotation, any "w" mode open | kernel.ts fdOpen never truncates; Node bridge does but WasmVM kernel doesn't | TRUE |
| 5 | **readdir missing "." and ".."** | tar, find, shell globbing, POSIX compliance | in-memory-fs.ts listDirEntries never synthesizes "." or ".." | TRUE |
| 6 | **Blocking flock() returns EAGAIN immediately** | File-based locks (databases, build tools) | file-lock.ts:61 comment: "Blocking not implemented — treat as EAGAIN" | TRUE |
| 7 | **Pipe write EAGAIN without O_NONBLOCK** | Large pipelines (`tar \| gzip \| aws s3 cp`) | pipe-manager.ts:107 throws EAGAIN on full buffer; no retry in fd_write handler | TRUE |
| 8 | **Unlink open file doesn't defer delete** | Temp file pattern (create, unlink, keep writing) | in-memory-fs.ts removeFile unconditionally deletes; no refcount check | TRUE |
| 9 | **No signal handlers** (sigaction/signal) | Servers, databases, graceful shutdown | No sigaction syscall; signals delivered by default actions only | TRUE |
| 10 | **WASM can't be interrupted mid-compute** | Ctrl+C during tight loops hangs | Tight loops bypass Atomics.wait; worker.terminate() is hard kill | TRUE |
| 11 | **All inodes are 0** | Hard link detection, backup tools, `find -inum` | in-memory-fs.ts lines 156,172,332 hardcode ino:0; no inode allocator | TRUE |

## TOO_THIN — Works for simple cases, breaks complex ones

Fact-check found 4 claims were FALSE or EXAGGERATED.

| # | Issue | What breaks | Verified | Notes |
|---|-------|-------------|----------|-------|
| 12 | **O_NONBLOCK not settable via fcntl F_SETFL** | Async I/O, event loops | ~~TRUE~~ **FALSE** | WasmVM fcntl.c DOES implement F_SETFL via __wasi_fd_fdstat_set_flags; only Node kernel lacks it |
| 13 | **PIPE_BUF atomicity not guaranteed** | Concurrent pipe writers | ~~TRUE~~ **FALSE** | JS is single-threaded — all pipe writes are atomic by definition; no interleaving possible |
| 14 | **pread/pwrite load entire file into memory** | Large file random access | ~~TRUE~~ **FALSE** | It's an InMemoryFS — files are already in memory; pread just slices a view. This is by design, not a bug |
| 15 | **/dev/ptmx stub** (doesn't allocate real PTY) | `script`, PTY-allocation | ~~TRUE~~ **FALSE** | Real PtyManager exists with full master/slave pairs, line discipline, and signal delivery. /dev/ptmx device-layer entry is VFS stub but PTY allocation happens via kernel ioctl |
| 16 | **poll() timeout -1 capped to 30s** | Event loops expecting indefinite blocking | **TRUE** | driver.ts:1098 hardcodes `timeout < 0 ? 30000 : timeout` |
| 17 | **No UDP sockets** | ping, DNS raw queries, DHCP | **TRUE** | SOCK_DGRAM accepted but silently creates TCP socket stub |
| 18 | **Socket send/recv ignore flags** (MSG_PEEK, MSG_DONTWAIT) | Protocol libraries | **TRUE** | Flags passed via RPC but never used in driver handlers |
| 19 | **setsockopt not implemented** (SO_REUSEADDR, TCP_NODELAY) | Port reuse, latency tuning | **TRUE** | kernel-worker.ts returns ENOSYS unconditionally |
| 20 | **Hard links don't increment nlink** | `ls -l`, backup dedup tools | **TRUE** | hardLinks Map tracked but stat always returns nlink:1 |
| 21 | **pthread_cond/barrier/rwlock/once not patched** | Python GIL, any C++ std::thread code | **TRUE** | Only mutex/key/attr patched; cond/barrier/rwlock/once use unpatched musl stubs that assume futex |
| 22 | **No iconv()** | Character set conversion | ~~TRUE~~ **FALSE** | musl's iconv IS compiled into wasi-libc; iconv.h available; charset support limited but present |
| 23 | **Timezone limited to UTC** | Locale-aware time formatting | **TRUE** | musl timezone code ifdef'd out for WASI; no tzdata in VFS; localtime returns UTC |
| 24 | **fcntl cloexec tracking limited to 256 FDs** | Programs with many open files | **TRUE** | fcntl.c:32 MAX_FDS=256; FDs >= 256 get EBADF |

## MISSING — Not implemented at all

Fact-check found several claims EXAGGERATED — the C interfaces exist but fail at WASI syscall layer (ENOSYS). The distinction matters: programs that check for availability at link time will succeed; programs that check at runtime will get clean errors.

| # | Issue | Verified | Notes |
|---|-------|----------|-------|
| 25 | No fork() | **EXAGGERATED** | fork() callable via musl but returns ENOSYS from WASI layer |
| 26 | No epoll | **EXAGGERATED** | epoll stubs exist in musl; fail with ENOSYS at SYS_epoll_create1 |
| 27 | No named pipes (mkfifo) | **EXAGGERATED** | mkfifo/mknod exist; fail with ENOSYS at SYS_mknodat |
| 28 | No shared memory | **EXAGGERATED** | shm_open/shmget exist; fail with ENOSYS at syscall layer |
| 29 | No /proc population | **TRUE** | /proc directory created but empty; no self/exe/cpuinfo |
| 30 | No mmap | **EXAGGERATED** | mmap available via `-lwasi-emulated-mman` emulation layer |

## What works well

- Text processing pipelines (grep, sed, awk, jq, sort)
- Shell scripting (bash 5.x compatible via brush-shell)
- Build systems (make with subcommand spawning)
- HTTP/HTTPS clients (curl, wget, git clone, npm install)
- Interactive terminals (real PTY with line discipline, Ctrl+C for children)
- File I/O basics (read, write, create, delete, rename)
- Cross-runtime process spawning (WasmVM <-> Node <-> Python)
- SQLite (single-threaded, file-based)
- iconv (character set conversion via musl)
- Full PTY allocation with master/slave pairs and signal delivery
- O_NONBLOCK settable via fcntl F_SETFL in WasmVM

## Corrected honest claim

> "POSIX shell scripts, text processing tools, and HTTP clients run unmodified. Server sockets (bind/listen/accept), custom signal handlers, and true multi-threading are not supported. Most POSIX C interfaces exist and link correctly but several kernel-level operations (O_EXCL, O_TRUNC, readdir ./.. , blocking flock, deferred unlink) are missing and will break programs that depend on them."

## Severity summary

- **11 confirmed WILL_BREAK** issues (all verified true)
- **9 confirmed TOO_THIN** issues (4 original claims debunked as false)
- **1 confirmed MISSING** issue (5 original claims were exaggerated — interfaces exist, ENOSYS at runtime)
- **4 claims were FALSE** (O_NONBLOCK works, PIPE_BUF atomic in JS, pread fine for InMemoryFS, real PTY exists)
- **1 claim was FALSE** (iconv exists in musl)
- **5 claims were EXAGGERATED** (fork/epoll/mkfifo/shm/mmap exist as C stubs returning ENOSYS)
3 changes: 2 additions & 1 deletion docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@
"group": "Reference",
"pages": [
"posix-compatibility",
"posix-conformance-report",
"os-test-conformance-report",
"libc-test-conformance-report",
"nodejs-conformance-report",
"python-compatibility"
]
Expand Down
60 changes: 60 additions & 0 deletions docs/libc-test-conformance-report.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
title: libc-test Conformance Report
description: musl libc-test kernel behavior conformance results for WasmVM.
icon: "chart-bar"
---

{/* AUTO-GENERATED — do not edit. Run scripts/generate-libc-test-report.ts */}

## Summary

musl libc-test tests actual kernel behavior — file locking, socket operations, stat edge cases,
and process management. Unlike os-test (which tests libc function correctness), these tests
exercise the runtime and kernel layer.

| Metric | Value |
| --- | --- |
| libc-test version | master |
| Total tests | 75 |
| Passing | 69 (92.0%) |
| Expected fail | 6 |
| Skip | 0 |
| Native verified | 68 of 69 passing tests verified against native output (98.6%) |
| Last updated | 2026-03-26 |

## Per-Suite Results

| Suite | Total | Pass | Fail | Skip | Pass Rate |
| --- | --- | --- | --- | --- | --- |
| functional | 41 | 37 | 4 | 0 | 90.2% |
| regression | 34 | 32 | 2 | 0 | 94.1% |
| **Total** | **75** | **69** | **6** | **0** | **100.0%** |

## Exclusions by Category

### WASM Limitations (4 entries)

Features impossible in wasm32-wasip1.

| Test | Reason | Issue |
| --- | --- | --- |
| `functional/dlopen_dso` | dlopen/dlsym not available in wasm32-wasip1 — no dynamic linking support | [#48](https://github.com/rivet-dev/secure-exec/issues/48) |
| `functional/tls_align_dso` | Thread-local storage with dynamic shared objects requires dlopen — not available in WASM | [#48](https://github.com/rivet-dev/secure-exec/issues/48) |
| `functional/tls_init_dso` | Thread-local storage initialization with DSOs requires dlopen — not available in WASM | [#48](https://github.com/rivet-dev/secure-exec/issues/48) |
| `regression/tls_get_new-dtv_dso` | TLS dynamic thread vector with DSOs requires dlopen — not available in WASM | [#48](https://github.com/rivet-dev/secure-exec/issues/48) |

### WASI Gaps (1 entry)

WASI Preview 1 lacks the required syscall.

| Test | Reason | Issue |
| --- | --- | --- |
| `regression/statvfs` | statvfs/fstatvfs not part of WASI — no filesystem statistics interface | [#48](https://github.com/rivet-dev/secure-exec/issues/48) |

### Implementation Gaps (1 entry)

Features we should support but don't yet. Each has a tracking issue.

| Test | Reason | Issue |
| --- | --- | --- |
| `functional/strptime` | strptime fails on timezone-related format specifiers (%Z, %z) — musl timezone code is ifdef'd out for WASI | [#48](https://github.com/rivet-dev/secure-exec/issues/48) |
Loading
Loading