-
Notifications
You must be signed in to change notification settings - Fork 2
Add a basic README + geteuid_grate example for documentation #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
rennergade
merged 5 commits into
Lind-Project:main
from
stupendoussuperpowers:update-readme.md
Dec 31, 2025
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
51cd23f
Add a basic README + geteuid-grate example.
stupendoussuperpowers b89cd20
Refactor grate execution path and add grate test examples (#472)
Yaxuan-w 2ee4687
Refactor and unify glibc 3i/legacy syscall path (#535)
Yaxuan-w 1577145
Update README.md and geteuid_grate.c comments.
stupendoussuperpowers 0373034
Address comments.
stupendoussuperpowers File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. | ||
|
|
||
| [`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``` | ||
|
|
||
|
stupendoussuperpowers marked this conversation as resolved.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ENTRY=geteuid_grate.c |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
|
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(); | ||
|
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; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | ||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.