Skip to content

Commit 6bb38ae

Browse files
authored
Merge pull request #3 from stupendoussuperpowers/update-readme.md
Add a basic README + geteuid_grate example for documentation
2 parents 6d9d70e + 0373034 commit 6bb38ae

File tree

5 files changed

+286
-2
lines changed

5 files changed

+286
-2
lines changed

README.md

Lines changed: 115 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,115 @@
1-
# lind-wasm-example-grates
2-
Example grates for Lind Wasm
1+
# Example Grates for Lind
2+
3+
This reposistory contains a collection of example grate implementations that can be used with the [Lind runtime](https://github.com/Lind-Project/lind-wasm)
4+
5+
Grates provide custom syscall wrappers for Lind cages. Each example grate here demonstrates how to override one or more syscalls with a custom implementation.
6+
7+
For more details, refer to the documentation here:
8+
9+
- [Lind-Wasm documentation](https://lind-project.github.io/lind-wasm/)
10+
- [3i](https://github.com/Lind-Project/lind-wasm/blob/main/src/threei/README.md)
11+
12+
## Repository Structure
13+
14+
Each directory under `examples/` contains a standalone grate implementation.
15+
16+
For a grate written in `C`, the typical structure for an individual grate is:
17+
18+
```
19+
examples/<name>-grate
20+
├── src/ // .c and .h source files.
21+
├── tests/ // Tests for this grate.
22+
├── build.conf // Configuration file to describe additional build flags, `--max-memory` for wasm, and entry point for the grate.
23+
├── compile_grate.sh // Compile script to generate *.wasm and *.cwasm binaries
24+
└── README.md
25+
```
26+
27+
## Writing a Grate
28+
29+
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.
30+
31+
Using the example in `examples/geteuid-grate` to illustrate this process:
32+
33+
**Registering Syscall Handlers:**
34+
35+
First, define a custom implementation of the syscall.
36+
37+
```c
38+
int geteuid_grate(uint64_t cageid) {
39+
return 10;
40+
}
41+
```
42+
43+
Next, register this function as the handler for `geteuid` using the `register_handler` function
44+
45+
```c
46+
// Fork a child process
47+
pid_t pid = fork();
48+
if (pid == 0) {
49+
int cageid = getpid();
50+
51+
// Register our custom handler
52+
uint64_t fn_ptr_addr = (uint64_t)(uintptr_t_) &geteuid_grate;
53+
register_handler(cageid, 107, 1, grateid, fn_ptr_addr);
54+
55+
// Run the cage (provided as argv[1])
56+
execv(argv[1], &argv[1]);
57+
}
58+
```
59+
60+
**Dispatch Handling:**
61+
62+
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.
63+
64+
The dispatcher is invoked with:
65+
- Function pointer registered for this syscall,
66+
- Calling cage id,
67+
- Syscall arguments (and their associated cage IDs)
68+
69+
```c
70+
int pass_fptr_to_wt(uint64_t fn_ptr_uint, uint64_t cageid, uint64_t arg1,
71+
uint64_t arg1cage, uint64_t arg2, uint64_t arg2cage,
72+
uint64_t arg3, uint64_t arg3cage, uint64_t arg4,
73+
uint64_t arg4cage, uint64_t arg5, uint64_t arg5cage,
74+
uint64_t arg6, uint64_t arg6cage) {
75+
76+
if (fn_ptr_uint == 0) {
77+
return -1;
78+
}
79+
80+
// Extract the function based on the function pointer that was passed.
81+
// This is the same address that was passed to the register_handler function.
82+
int (*fn)(uint64_t) = (int (*)(uint64_t))(uintptr_t)fn_ptr_uint;
83+
84+
// In this case, we only pass down the cageid as the argument for the geteuid syscall.
85+
return fn(cageid);
86+
}
87+
```
88+
89+
**Process Coordination:**
90+
91+
Each grate must invoke `execv(argv[1], &argv[1])` exactly once, after registering its syscall handlers.
92+
93+
This design avoids centralized process coordination. Once `execv` is called, further process creation or handler registrations are the responsibility of the executed cage.
94+
95+
This also allows multiple grates to be interposed. For example:
96+
97+
```lind_run geteuid_grate.wasm getuid_grate.wasm example.wasm```
98+
99+
100+
## Compiling a Grate
101+
102+
Grates are compiled similarly to standard Lind programs, with the additional requirement that the WASM module exports the `pass_fptr_to_wt` function.
103+
104+
[`lind_compile`](https://github.com/Lind-Project/lind-wasm/blob/main/scripts/lind_compile) script compiles `.c` programs to `.wasm` binaries for lind.
105+
106+
Example of a compile script: [`examples/geteuid-grate/compile_grate.sh`](./examples/geteuid-grate/compile_grate.sh)
107+
108+
## Running a Grate
109+
110+
Grates are executed like standard Lind programs, that expect cage binaries to be present at `argv[1]`.
111+
112+
Example usage:
113+
114+
```lind_run geteuid_grate.wasm example.wasm```
115+

examples/geteuid-grate/build.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ENTRY=geteuid_grate.c
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
if [[ $# -ne 1 ]]; then
5+
echo "usage: $0 <example-dir>"
6+
exit 1
7+
fi
8+
9+
TARGET="$1"
10+
11+
# Enter the example directory
12+
pushd "$TARGET" >/dev/null
13+
14+
# Now everything is relative to the example dir
15+
echo "[cwd] $(pwd)"
16+
17+
# Load per-example config
18+
if [[ ! -f build.conf ]]; then
19+
echo "missing build.conf"
20+
exit 1
21+
fi
22+
source build.conf
23+
24+
CLANG="${CLANG:-/home/lind/lind-wasm/clang+llvm-18.1.8-x86_64-linux-gnu-ubuntu-18.04/bin/clang}"
25+
SYSROOT="${SYSROOT:-/home/lind/lind-wasm/src/glibc/sysroot}"
26+
WASM_OPT="${WASM_OPT:-/home/lind/lind-wasm/tools/binaryen/bin/wasm-opt}"
27+
WASMTIME="${WASMTIME:-/home/lind/lind-wasm/src/wasmtime/target/release/wasmtime}"
28+
29+
SRC_DIR="src"
30+
mkdir -p output
31+
OUT="output/${ENTRY%.c}"
32+
33+
MAX_MEMORY="${MAX_MEMORY:-268435456}"
34+
EXTRA_CFLAGS="${EXTRA_CFLAGS:-}"
35+
EXTRA_WASM_OPT="${EXTRA_WASM_OPT:-}"
36+
37+
echo "[build] $OUT (max-mem=$MAX_MEMORY)"
38+
39+
"$CLANG" -pthread \
40+
--target=wasm32-unknown-wasi \
41+
--sysroot "$SYSROOT" \
42+
-Wl,--import-memory,--export-memory,--max-memory="$MAX_MEMORY",\
43+
--export=__stack_pointer,--export=__stack_low,--export=pass_fptr_to_wt \
44+
$EXTRA_CFLAGS \
45+
"$SRC_DIR"/*.c \
46+
-g -O0 -o "$OUT.wasm"
47+
48+
"$WASM_OPT" \
49+
--asyncify \
50+
--epoch-injection \
51+
--debuginfo \
52+
$EXTRA_WASM_OPT \
53+
"$OUT.wasm" -o "$OUT.wasm"
54+
55+
"$WASMTIME" compile "$OUT.wasm" -o "$OUT.cwasm"
56+
57+
# Return to original directory
58+
popd >/dev/null
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#include <errno.h>
2+
// lind_syscall.h provides functions needed for cage interactions: register_handler, copy_data_between_cage, and make_threei_call
3+
#include <lind_syscall.h>
4+
#include <stdio.h>
5+
#include <stdlib.h>
6+
#include <sys/types.h>
7+
#include <sys/wait.h>
8+
#include <unistd.h>
9+
10+
// Dispatcher function
11+
//
12+
// Entry point into a grate when a child cage invokes a registered
13+
// syscall. This function is used to invoke the appropriate handler,
14+
// and value returned is the passed down to the calling cage.
15+
//
16+
// Args:
17+
// fn_ptr_uint Address of the registered syscall handler within the
18+
// grate's address space.
19+
// cageid Identifier of the calling cage.
20+
// arg[1-6] Syscall arguments. Numeric types are passed by value, pointers
21+
// are passed as addresses in the originating cage's address space.
22+
// arg[1-6]cage Cage IDs corresponding to each argument, indicating which cage's address
23+
// space the argument belongs to.
24+
int pass_fptr_to_wt(uint64_t fn_ptr_uint, uint64_t cageid, uint64_t arg1,
25+
uint64_t arg1cage, uint64_t arg2, uint64_t arg2cage,
26+
uint64_t arg3, uint64_t arg3cage, uint64_t arg4,
27+
uint64_t arg4cage, uint64_t arg5, uint64_t arg5cage,
28+
uint64_t arg6, uint64_t arg6cage) {
29+
if (fn_ptr_uint == 0) {
30+
fprintf(stderr, "[Grate|geteuid] Invalid function ptr\n");
31+
return -1;
32+
}
33+
34+
printf("[Grate|geteuid] Handling function ptr: %llu from cage: %llu\n",
35+
fn_ptr_uint, cageid);
36+
37+
int (*fn)(uint64_t) = (int (*)(uint64_t))(uintptr_t)fn_ptr_uint;
38+
39+
return fn(cageid);
40+
}
41+
42+
// Function ptr and signatures of this grate
43+
int geteuid_grate(uint64_t);
44+
45+
int geteuid_grate(uint64_t cageid) {
46+
printf("[Grate|geteuid] In geteuid_grate %d handler for cage: %llu\n",
47+
getpid(), cageid);
48+
return 10;
49+
}
50+
51+
// Main function will always be same in all grates
52+
int main(int argc, char *argv[]) {
53+
// Should be at least two inputs (at least one grate file and one cage file)
54+
if (argc < 2) {
55+
fprintf(stderr, "Usage: %s <cage_file>\n", argv[0]);
56+
exit(EXIT_FAILURE);
57+
}
58+
59+
int grateid = getpid();
60+
61+
// Because we assume that all cages are unaware of the existence of grate,
62+
// cages will not handle the logic of `exec`ing grate.
63+
// Instead, a grate instance is responsible for mananging this.
64+
//
65+
// It forks and execs exactly once: To execute the child binary provided
66+
// as argv[1], passing argv[1..] as that program's command-line arguments.
67+
// Any further process management is handled by this executed program, not
68+
// by the original grate.
69+
70+
pid_t pid = fork();
71+
if (pid < 0) {
72+
perror("fork failed");
73+
exit(EXIT_FAILURE);
74+
} else if (pid == 0) {
75+
int cageid = getpid();
76+
// Set the geteuid (syscallnum=107) of this cage to call this grate
77+
// function geteuid_grate (func index=0)
78+
// Syntax of register_handler:
79+
// register_handler(
80+
// int64_t targetcage, - Cage ID to be intercepted
81+
// uint64_t targetcallnum, - Syscall number to be intercepted
82+
// uint64_t handlefunc_flag, - 0 for deregister non-0 for register
83+
// uint64_t this_grate_id, - Grate ID to redirect call to
84+
// uint64_t optional_arg - Handler function pointer if registering
85+
// )
86+
uint64_t fn_ptr_addr = (uint64_t)(uintptr_t)&geteuid_grate;
87+
printf("[Grate|geteuid] Registering geteuid handler for cage %d in "
88+
"grate %d with fn ptr addr: %llu\n",
89+
cageid, grateid, fn_ptr_addr);
90+
int ret = register_handler(cageid, 107, 1, grateid, fn_ptr_addr);
91+
92+
if (execv(argv[1], &argv[1]) == -1) {
93+
perror("execv failed");
94+
exit(EXIT_FAILURE);
95+
}
96+
}
97+
98+
int status;
99+
while (wait(&status) > 0) {
100+
printf("[Grate|geteuid] terminated, status: %d\n", status);
101+
}
102+
103+
return 0;
104+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#include <stdio.h>
2+
#include <unistd.h>
3+
4+
int main(int argc, char *argv[]) {
5+
int ret = geteuid();
6+
printf("[Cage | geteuid] geteuid ret = %d\n", ret);
7+
return 0;
8+
}

0 commit comments

Comments
 (0)