Skip to content

Lind as a shared library - #1294

Open
vidyalakshmir wants to merge 9 commits into
mainfrom
lind-shared-lib
Open

Lind as a shared library#1294
vidyalakshmir wants to merge 9 commits into
mainfrom
lind-shared-lib

Conversation

@vidyalakshmir

Copy link
Copy Markdown
Contributor

Sandbox a native library: run a wasm-compiled library as a drop-in .so

Summary

Adds the plumbing to compile a library to WebAssembly, run it as a guest inside the lind/wasmtime sandbox, and expose it as an ordinary native .so. An unmodified native app links the .so and calls its functions normally, unaware the work happens inside a wasm sandbox.

This delivers the first end-to-end slice: a libadd_sub.so whose add/subtract run inside a wasm cage. Functions are scalar-only, so this proves the runtime + packaging plumbing in isolation, before any data marshalling.

Components

lind-boot embedding API
Refactors startup so the same path can either run an entry point (the binary) or hand back a live instance (a library):

PoC + build harness (lind-sharedlib-poc/)
One self-contained folder per iteration under examples/. 01-scalars has the guest library, an unmodified native demo.c, and the cdylib stub crate. tools/gen_stubs.sh generates the extern "C" stubs from a functions.txt manifest; the guest module is staged into lindfs/ via lind_compile --output-dir.

Build and run

Prereq: make build once at repo root.

cd lind-sharedlib-poc/examples/01-scalars
make            # build only (build & run can be on different machines)
make run        # run the wasm-sandboxed libadd_sub.so
make run-native # baseline: real native lib, no sandbox

Both print add(2, 3) = 5 / subtract(10, 4) = 6.

Scope

Currently works for libraries with functions with scalar arguments.

Next steps : Support for pointer, buffer, string, struct, callback marshaling, per-thread concurrency, stub codegen from headers.

@Yaxuan-w
Yaxuan-w self-requested a review July 2, 2026 02:14
// initial cage is still alive. Finalize it ourselves (the same
// teardown the crash path performs) so `lind_manager.wait()` does
// not block forever waiting for a cage that never exits.
if is_sandboxed_lib_call {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

execute_wasmtime() still interprets the first returned Val::I32 as the process exit code. In normal _start mode that makes sense, but in --call add ... 2 3 mode the return value is the function result, i.e. 5. That means a successful lind_run --call add ... exits with status 5, which is abnormal for the lind-wasm runtime exit. Moreover, the new make check target will also fail even though the call succeeded.

In --call mode the return values should be printed/returned to the caller, but the process exit status should be 0 unless invocation failed.

static RUNTIME_INIT: std::sync::OnceLock<()> = std::sync::OnceLock::new();
RUNTIME_INIT.get_or_init(|| {
// Bring up RawPOSIX and register its syscalls with 3i.
rawposix::init::rawposix_start(0);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We don't chroot here.. Should we also have fs isolation on lib mode?

let lind_manager = Arc::new(LindCageManager::new(0));
lind_manager.increment();

let cageid = CAGE_START_ID as u64;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We always use CAGE_START_ID while RawPOSIX/3i/cage state is process-global. This works for exactly one generated .so, but if a host loads two sandboxed libraries, or two different wrappers both call init_sandboxed_lib, they will both try to install state for the same cage id.

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.

2 participants