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
117 changes: 115 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,115 @@
# lind-wasm-example-grates
Example grates for Lind Wasm
# Example Grates for Lind

This reposistory contains a collection of example grate implementations that can be used with the [Lind runtime](https://github.com/Lind-Project/lind-wasm)

Grates provide custom syscall wrappers for Lind cages. Each example grate here demonstrates how to override one or more syscalls with a custom implementation.

For more details, refer to the documentation here:

- [Lind-Wasm documentation](https://lind-project.github.io/lind-wasm/)
- [3i](https://github.com/Lind-Project/lind-wasm/blob/main/src/threei/README.md)

## Repository Structure

Each directory under `examples/` contains a standalone grate implementation.

For a grate written in `C`, the typical structure for an individual grate is:

```
examples/<name>-grate
├── src/ // .c and .h source files.
├── tests/ // Tests for this grate.
├── build.conf // Configuration file to describe additional build flags, `--max-memory` for wasm, and entry point for the grate.
├── compile_grate.sh // Compile script to generate *.wasm and *.cwasm binaries
└── README.md
```

## Writing a Grate

By default, syscalls invoked by a cage are forwarded to `rawposix`. A grate allows selected syscalls from a child cage to be intercepted and handled by custom functions.

Using the example in `examples/geteuid-grate` to illustrate this process:

**Registering Syscall Handlers:**

First, define a custom implementation of the syscall.

```c
int geteuid_grate(uint64_t cageid) {
return 10;
}
```

Next, register this function as the handler for `geteuid` using the `register_handler` function

```c
// Fork a child process
pid_t pid = fork();
if (pid == 0) {
int cageid = getpid();

// Register our custom handler
uint64_t fn_ptr_addr = (uint64_t)(uintptr_t_) &geteuid_grate;
register_handler(cageid, 107, 1, grateid, fn_ptr_addr);

// Run the cage (provided as argv[1])
execv(argv[1], &argv[1]);
}
```

**Dispatch Handling:**

Each grate must define a dispatcher function named `pass_fptr_to_wt` which serves as the entry point for all intercepted syscalls in that grate.

The dispatcher is invoked with:
- Function pointer registered for this syscall,
- Calling cage id,
- Syscall arguments (and their associated cage IDs)

```c
int pass_fptr_to_wt(uint64_t fn_ptr_uint, uint64_t cageid, uint64_t arg1,
uint64_t arg1cage, uint64_t arg2, uint64_t arg2cage,
uint64_t arg3, uint64_t arg3cage, uint64_t arg4,
uint64_t arg4cage, uint64_t arg5, uint64_t arg5cage,
uint64_t arg6, uint64_t arg6cage) {

if (fn_ptr_uint == 0) {
return -1;
}

// Extract the function based on the function pointer that was passed.
// This is the same address that was passed to the register_handler function.
int (*fn)(uint64_t) = (int (*)(uint64_t))(uintptr_t)fn_ptr_uint;

// In this case, we only pass down the cageid as the argument for the geteuid syscall.
return fn(cageid);
}
```

**Process Coordination:**

Each grate must invoke `execv(argv[1], &argv[1])` exactly once, after registering its syscall handlers.

This design avoids centralized process coordination. Once `execv` is called, further process creation or handler registrations are the responsibility of the executed cage.

This also allows multiple grates to be interposed. For example:

```lind_run geteuid_grate.wasm getuid_grate.wasm example.wasm```


## Compiling a Grate

Grates are compiled similarly to standard Lind programs, with the additional requirement that the WASM module exports the `pass_fptr_to_wt` function.
Comment thread
stupendoussuperpowers marked this conversation as resolved.

[`lind_compile`](https://github.com/Lind-Project/lind-wasm/blob/main/scripts/lind_compile) script compiles `.c` programs to `.wasm` binaries for lind.

Example of a compile script: [`examples/geteuid-grate/compile_grate.sh`](./examples/geteuid-grate/compile_grate.sh)

## Running a Grate

Grates are executed like standard Lind programs, that expect cage binaries to be present at `argv[1]`.

Example usage:

```lind_run geteuid_grate.wasm example.wasm```

1 change: 1 addition & 0 deletions examples/geteuid-grate/build.conf
Comment thread
stupendoussuperpowers marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ENTRY=geteuid_grate.c
58 changes: 58 additions & 0 deletions examples/geteuid-grate/compile_grate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env bash
set -euo pipefail

if [[ $# -ne 1 ]]; then
echo "usage: $0 <example-dir>"
exit 1
fi

TARGET="$1"

# Enter the example directory
pushd "$TARGET" >/dev/null

# Now everything is relative to the example dir
echo "[cwd] $(pwd)"

# Load per-example config
if [[ ! -f build.conf ]]; then
echo "missing build.conf"
exit 1
fi
source build.conf

CLANG="${CLANG:-/home/lind/lind-wasm/clang+llvm-18.1.8-x86_64-linux-gnu-ubuntu-18.04/bin/clang}"
SYSROOT="${SYSROOT:-/home/lind/lind-wasm/src/glibc/sysroot}"
WASM_OPT="${WASM_OPT:-/home/lind/lind-wasm/tools/binaryen/bin/wasm-opt}"
WASMTIME="${WASMTIME:-/home/lind/lind-wasm/src/wasmtime/target/release/wasmtime}"

SRC_DIR="src"
mkdir -p output
OUT="output/${ENTRY%.c}"

MAX_MEMORY="${MAX_MEMORY:-268435456}"
EXTRA_CFLAGS="${EXTRA_CFLAGS:-}"
EXTRA_WASM_OPT="${EXTRA_WASM_OPT:-}"

echo "[build] $OUT (max-mem=$MAX_MEMORY)"

"$CLANG" -pthread \
--target=wasm32-unknown-wasi \
--sysroot "$SYSROOT" \
-Wl,--import-memory,--export-memory,--max-memory="$MAX_MEMORY",\
--export=__stack_pointer,--export=__stack_low,--export=pass_fptr_to_wt \
$EXTRA_CFLAGS \
"$SRC_DIR"/*.c \
-g -O0 -o "$OUT.wasm"

"$WASM_OPT" \
--asyncify \
--epoch-injection \
--debuginfo \
$EXTRA_WASM_OPT \
"$OUT.wasm" -o "$OUT.wasm"

"$WASMTIME" compile "$OUT.wasm" -o "$OUT.cwasm"

# Return to original directory
popd >/dev/null
104 changes: 104 additions & 0 deletions examples/geteuid-grate/src/geteuid_grate.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#include <errno.h>
// lind_syscall.h provides functions needed for cage interactions: register_handler, copy_data_between_cage, and make_threei_call
#include <lind_syscall.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

// Dispatcher function
Comment thread
stupendoussuperpowers marked this conversation as resolved.
//
// Entry point into a grate when a child cage invokes a registered
// syscall. This function is used to invoke the appropriate handler,
// and value returned is the passed down to the calling cage.
//
// Args:
// fn_ptr_uint Address of the registered syscall handler within the
// grate's address space.
// cageid Identifier of the calling cage.
// arg[1-6] Syscall arguments. Numeric types are passed by value, pointers
// are passed as addresses in the originating cage's address space.
// arg[1-6]cage Cage IDs corresponding to each argument, indicating which cage's address
// space the argument belongs to.
int pass_fptr_to_wt(uint64_t fn_ptr_uint, uint64_t cageid, uint64_t arg1,
uint64_t arg1cage, uint64_t arg2, uint64_t arg2cage,
uint64_t arg3, uint64_t arg3cage, uint64_t arg4,
uint64_t arg4cage, uint64_t arg5, uint64_t arg5cage,
uint64_t arg6, uint64_t arg6cage) {
if (fn_ptr_uint == 0) {
fprintf(stderr, "[Grate|geteuid] Invalid function ptr\n");
return -1;
}

printf("[Grate|geteuid] Handling function ptr: %llu from cage: %llu\n",
fn_ptr_uint, cageid);

int (*fn)(uint64_t) = (int (*)(uint64_t))(uintptr_t)fn_ptr_uint;

return fn(cageid);
}

// Function ptr and signatures of this grate
int geteuid_grate(uint64_t);

int geteuid_grate(uint64_t cageid) {
printf("[Grate|geteuid] In geteuid_grate %d handler for cage: %llu\n",
getpid(), cageid);
return 10;
}

// Main function will always be same in all grates
int main(int argc, char *argv[]) {
// Should be at least two inputs (at least one grate file and one cage file)
if (argc < 2) {
fprintf(stderr, "Usage: %s <cage_file>\n", argv[0]);
exit(EXIT_FAILURE);
}

int grateid = getpid();

// Because we assume that all cages are unaware of the existence of grate,
// cages will not handle the logic of `exec`ing grate.
// Instead, a grate instance is responsible for mananging this.
//
// It forks and execs exactly once: To execute the child binary provided
// as argv[1], passing argv[1..] as that program's command-line arguments.
// Any further process management is handled by this executed program, not
// by the original grate.

pid_t pid = fork();
Comment thread
rennergade marked this conversation as resolved.
if (pid < 0) {
perror("fork failed");
exit(EXIT_FAILURE);
} else if (pid == 0) {
int cageid = getpid();
// Set the geteuid (syscallnum=107) of this cage to call this grate
// function geteuid_grate (func index=0)
// Syntax of register_handler:
// register_handler(
// int64_t targetcage, - Cage ID to be intercepted
// uint64_t targetcallnum, - Syscall number to be intercepted
// uint64_t handlefunc_flag, - 0 for deregister non-0 for register
// uint64_t this_grate_id, - Grate ID to redirect call to
// uint64_t optional_arg - Handler function pointer if registering
// )
uint64_t fn_ptr_addr = (uint64_t)(uintptr_t)&geteuid_grate;
printf("[Grate|geteuid] Registering geteuid handler for cage %d in "
"grate %d with fn ptr addr: %llu\n",
cageid, grateid, fn_ptr_addr);
int ret = register_handler(cageid, 107, 1, grateid, fn_ptr_addr);

if (execv(argv[1], &argv[1]) == -1) {
perror("execv failed");
exit(EXIT_FAILURE);
}
}

int status;
while (wait(&status) > 0) {
printf("[Grate|geteuid] terminated, status: %d\n", status);
}

return 0;
}
8 changes: 8 additions & 0 deletions examples/geteuid-grate/test/geteuid.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
int ret = geteuid();
printf("[Cage | geteuid] geteuid ret = %d\n", ret);
return 0;
}