Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b9f07f5
research(fspy): evaluate SIGSYS interception
wan9chi Aug 2, 2026
d65b8db
fix(fspy): rendezvous after exec event
wan9chi Aug 2, 2026
2a7028f
docs(fspy): record x86 SIGSYS results
wan9chi Aug 2, 2026
1c54867
test(fspy): validate recursive ptrace with Vitest browser
wan9chi Aug 2, 2026
d694e29
fix(fspy): notify the ptrace supervisor
wan9chi Aug 2, 2026
e92edf2
test(fspy): trace recursive SIGSYS dispatch
wan9chi Aug 2, 2026
d65c4ee
fix(fspy): unblock SIGSYS before recursive exec
wan9chi Aug 2, 2026
6c6e835
test(fspy): install Chromium for ptrace validation
wan9chi Aug 2, 2026
7edb513
test(fspy): trap file syscalls in browser proof
wan9chi Aug 2, 2026
8162dc9
docs(fspy): record Vitest browser ptrace validation
wan9chi Aug 2, 2026
294cd60
test(fspy): enable Chromium sandbox validation
wan9chi Aug 2, 2026
9ad7c47
test(fspy): allow Chromium sandbox in CI
wan9chi Aug 2, 2026
9fd5a47
fix(fspy): forward foreign seccomp traps
wan9chi Aug 2, 2026
f630702
fix(fspy): permit nested SIGSYS dispatch
wan9chi Aug 2, 2026
f3ad169
test(fspy): isolate Chromium sandbox prerequisites
wan9chi Aug 2, 2026
6fc1b04
test(fspy): diagnose Chromium zygote handshake
wan9chi Aug 2, 2026
07b5430
test(fspy): isolate Chromium exec trapping
wan9chi Aug 2, 2026
73077ce
test(fspy): isolate SIGSYS mask virtualization
wan9chi Aug 2, 2026
b414304
test(fspy): retry Chromium zygote handshake
wan9chi Aug 2, 2026
7ce9372
docs(fspy): record Chromium sandbox boundary
wan9chi Aug 2, 2026
59b8ad7
test(fspy): assert Chromium sandbox boundary
wan9chi Aug 2, 2026
5da3a6c
feat(fspy): prototype Rust handler blob
wan9chi Aug 2, 2026
67f0030
docs(fspy): record Rust handler execution proof
wan9chi Aug 2, 2026
95e5551
chore: merge main into SIGSYS research
wan9chi Aug 2, 2026
61df44b
feat(fspy): prototype ptrace SIGSYS backend
wan9chi Aug 2, 2026
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
234 changes: 234 additions & 0 deletions .github/workflows/fspy-sigsys-research.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
name: fspy SIGSYS research

permissions:
contents: read

on:
workflow_dispatch:
pull_request:
paths:
- '.github/workflows/fspy-sigsys-research.yml'
- 'crates/vite_task_bin/tests/e2e_snapshots/fixtures/vitest_browser_cache/**'
- 'research/ptrace-exec-prototype/**'
- 'research/rust-injected-runtime/**'
- 'research/sigsys-prototype/**'

defaults:
run:
shell: bash

jobs:
native-x86:
name: Native x86-64
runs-on: ubuntu-24.04
steps:
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2

- uses: oxc-project/setup-rust@3d6fb132fbe7cdcb66bf8ec193911c2945369d12 # v1.0.17
with:
components: llvm-tools

- name: Build and audit the freestanding Rust handler blob
run: |
rustup target add x86_64-unknown-linux-musl aarch64-unknown-linux-musl
make -C research/rust-injected-runtime check

- name: Run syscall and exec prototypes
run: |
set -euxo pipefail
test "$(uname -m)" = x86_64

probe_dir=$(mktemp -d)
cp -a research/sigsys-prototype "$probe_dir/sigsys"
cp -a research/ptrace-exec-prototype "$probe_dir/ptrace"

cd "$probe_dir/sigsys"
cc -O2 -Wall -Wextra -Werror -pthread trap_bench.c -o trap_bench
./trap_bench
cc -O2 -Wall -Wextra -Werror unotify_bench.c -o unotify_bench
./unotify_bench
cc -O2 -Wall -Wextra -Werror reexec_bootstrap.c -o reexec_bootstrap
./reexec_bootstrap

cd "$probe_dir/ptrace"
make check
timeout 30s ./recursive_injector /bin/true
cc -O2 -Wall -Wextra -Werror -pthread -std=gnu11 \
nonleader_exec.c -o nonleader_exec
./nonleader_exec /bin/true

vitest-browser-x86:
name: Chromium sandbox compatibility boundary
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2

- uses: oxc-project/setup-node@4c588e9266bd930b6ddc34307df0659ed511d187 # v1.3.1

- name: Install Chromium system dependencies
run: pnpm --filter vite-task-tools exec playwright install --with-deps chromium

- name: Enable Chromium's user-namespace sandbox
run: |
set -euxo pipefail
sysctl kernel.unprivileged_userns_clone
sysctl kernel.apparmor_restrict_unprivileged_userns
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0

- name: Verify the CI host can launch sandboxed Chromium
run: |
set -euxo pipefail
sandbox_control='
const { chromium } = await import("playwright");
const browser = await chromium.launch({
headless: true,
chromiumSandbox: true,
});
const page = await browser.newPage();
await page.setContent("<p>sandbox control</p>");
await browser.close();
'
pnpm --filter vite-task-tools exec node --input-type=module \
--eval "$sandbox_control"
setpriv --no-new-privs \
pnpm --filter vite-task-tools exec node --input-type=module \
--eval "$sandbox_control"

- name: Validate Vitest browser modes through the exec bridge
run: |
set -euxo pipefail
test "$(uname -m)" = x86_64

make -C research/ptrace-exec-prototype recursive_injector
probe_dir=$(mktemp -d)
cp -a crates/vite_task_bin/tests/e2e_snapshots/fixtures/vitest_browser_cache \
"$probe_dir/default"
cp -a crates/vite_task_bin/tests/e2e_snapshots/fixtures/vitest_browser_cache \
"$probe_dir/sandboxed"
ln -s "$PWD/packages/tools/node_modules" "$probe_dir/node_modules"

injector="$PWD/research/ptrace-exec-prototype/recursive_injector"
vitest="$PWD/packages/tools/node_modules/.bin/vitest"

cd "$probe_dir/default"
VITEST_CHROMIUM_SANDBOX=false \
DEBUG=vitest:browser:playwright,pw:browser \
timeout 120s "$injector" "$vitest" run \
2>&1 | tee "$probe_dir/default.log"
test -s dist/result.json
grep -F '"success":true' dist/result.json
grep -F 'chromiumSandbox: false' "$probe_dir/default.log"
grep -E 'bridge: injected exec .*exe=.*/(chrome|headless_shell)' \
"$probe_dir/default.log"
grep -E 'bridge: summary injected_execs=[2-9][0-9]* failed_execs=0' \
"$probe_dir/default.log"

cd "$probe_dir/sandboxed"

sandbox_record="$probe_dir/chromium-sandbox.txt"
no_sandbox_record="$probe_dir/chromium-no-sandbox.txt"
(
set +x
while :; do
for cmdline_file in /proc/[0-9]*/cmdline; do
test -r "$cmdline_file" || continue
command_line=$(tr '\0' ' ' < "$cmdline_file" 2>/dev/null || true)
case "$command_line" in
*chrome-headless-shell*) ;;
*) continue ;;
esac
case "$command_line" in
*--no-sandbox*)
printf '%s\n' "$command_line" > "$no_sandbox_record"
;;
esac
case "$command_line" in
*--type=renderer*) ;;
*) continue ;;
esac

pid=${cmdline_file#/proc/}
pid=${pid%/cmdline}
filters=$(awk '/^Seccomp_filters:/ { print $2 }' \
"/proc/$pid/status" 2>/dev/null || true)
if test "${filters:-0}" -ge 2; then
{
printf 'pid=%s\ncmdline=%s\n' "$pid" "$command_line"
awk '/^(NoNewPrivs|Seccomp|Seccomp_filters):/ { print }' \
"/proc/$pid/status" 2>/dev/null || true
} > "$sandbox_record"
exit 0
fi
done
sleep 0.02
done
) &
observer_pid=$!

set +e
VITEST_CHROMIUM_SANDBOX=true \
DEBUG=vitest:browser:playwright,pw:browser \
timeout 120s "$injector" "$vitest" run \
2>&1 | tee "$probe_dir/bridge.log"
run_status=${PIPESTATUS[0]}
set -e

kill "$observer_pid" 2>/dev/null || true
wait "$observer_pid" 2>/dev/null || true
test "$run_status" -ne 0

test -s dist/result.json
grep -F 'chromiumSandbox: true' "$probe_dir/bridge.log"
test ! -s "$no_sandbox_record"
test ! -s "$sandbox_record"
grep -E 'bridge: exec argv .* --type=zygote ' "$probe_dir/bridge.log"
grep -F 'bridge: zygote fd 3=socket:' "$probe_dir/bridge.log"
grep -F 'fspy: recvmsg returned EOF' "$probe_dir/bridge.log"
grep -F 'FATAL:content/browser/zygote_host/zygote_host_impl_linux.cc:207' \
"$probe_dir/bridge.log"
grep -F 'ZygoteMain: initializing' "$probe_dir/bridge.log"
grep -F 'write: Broken pipe (32)' "$probe_dir/bridge.log"
grep -E 'bridge: summary injected_execs=[2-9][0-9]* failed_execs=0' \
"$probe_dir/bridge.log"

docker-x86:
name: Docker default seccomp
runs-on: ubuntu-24.04
steps:
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2

- name: Run under an existing filter with no-new-privileges
run: |
set -euxo pipefail
docker run --rm \
--security-opt no-new-privileges \
-v "$PWD/research:/src:ro" \
gcc:14-bookworm \
sh -eux -c '
grep Seccomp /proc/self/status
grep NoNewPrivs /proc/self/status
cat /proc/self/attr/current || true

mkdir /tmp/sigsys /tmp/ptrace
cp /src/sigsys-prototype/trap_bench.c /tmp/sigsys/
cp /src/sigsys-prototype/unotify_bench.c /tmp/sigsys/
cp /src/sigsys-prototype/reexec_bootstrap.c /tmp/sigsys/
cp /src/ptrace-exec-prototype/injector.c /tmp/ptrace/
cp /src/ptrace-exec-prototype/nested_trap.c /tmp/ptrace/
cp /src/ptrace-exec-prototype/recursive_injector.c /tmp/ptrace/
cp /src/ptrace-exec-prototype/target.c /tmp/ptrace/
cp /src/ptrace-exec-prototype/Makefile /tmp/ptrace/

cd /tmp/sigsys
cc -O2 -Wall -Wextra -Werror -pthread trap_bench.c -o trap_bench
./trap_bench
cc -O2 -Wall -Wextra -Werror unotify_bench.c -o unotify_bench
./unotify_bench
cc -O2 -Wall -Wextra -Werror reexec_bootstrap.c -o reexec_bootstrap
./reexec_bootstrap

cd /tmp/ptrace
make check
timeout 30s ./recursive_injector /bin/true
'
3 changes: 3 additions & 0 deletions .typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
ratatui = "ratatui"
PUNICODE = "PUNICODE"

[default.extend-identifiers]
msg_controllen = "msg_controllen"

[files]
extend-exclude = [
"crates/fspy_detours_sys/detours",
Expand Down
3 changes: 1 addition & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/fspy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,12 @@
which = { workspace = true, features = ["tracing"] }

[target.'cfg(target_os = "linux")'.dependencies]
fspy_seccomp_unotify = { workspace = true, features = ["supervisor"] }
nix = { workspace = true, features = ["uio"] }

Check failure on line 26 in crates/fspy/Cargo.toml

View workflow job for this annotation

GitHub Actions / Format and Check Deps

shear/unused_dependency

unused dependency `nix` (remove this dependency)
tokio = { workspace = true, features = ["bytes"] }

[target.'cfg(unix)'.dependencies]
fspy_shared_unix = { workspace = true }
nix = { workspace = true, features = ["fs", "process", "socket", "feature"] }

Check failure on line 31 in crates/fspy/Cargo.toml

View workflow job for this annotation

GitHub Actions / Format and Check Deps

shear/unused_dependency

unused dependency `nix` (remove this dependency)

[target.'cfg(target_os = "windows")'.dependencies]
fspy_detours_sys = { workspace = true }
Expand Down Expand Up @@ -61,6 +60,7 @@
# builds are cheap.
[build-dependencies]
anyhow = { workspace = true }
cc = { workspace = true }
materialized_artifact_build = { workspace = true }
flate2 = { workspace = true }
fspy_preload_unix = { workspace = true }
Expand Down
17 changes: 17 additions & 0 deletions crates/fspy/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ fn fetch_macos_binaries(out_dir: &Path) -> anyhow::Result<()> {
fn register_preload_cdylib() -> anyhow::Result<()> {
let env_name = match env::var("CARGO_CFG_TARGET_OS").unwrap().as_str() {
"windows" => "CARGO_CDYLIB_FILE_FSPY_PRELOAD_WINDOWS",
"linux" => return Ok(()),
_ if env::var("CARGO_CFG_TARGET_ENV").unwrap() == "musl" => return Ok(()),
_ => "CARGO_CDYLIB_FILE_FSPY_PRELOAD_UNIX",
};
Expand All @@ -157,8 +158,24 @@ fn register_preload_cdylib() -> anyhow::Result<()> {
Ok(())
}

fn build_linux_sigsys_injector() {
if env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("linux")
|| env::var("CARGO_CFG_TARGET_ARCH").as_deref() != Ok("x86_64")
{
return;
}

println!("cargo:rerun-if-changed=src/unix/sigsys_x86_64.c");
cc::Build::new()
.file("src/unix/sigsys_x86_64.c")
.flag_if_supported("-std=c11")
.warnings(true)
.compile("fspy_sigsys_x86_64");
}

fn main() -> anyhow::Result<()> {
println!("cargo:rerun-if-changed=build.rs");
build_linux_sigsys_injector();
let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
fetch_macos_binaries(&out_dir).context("Failed to fetch macOS binaries")?;
register_preload_cdylib().context("Failed to register preload cdylib")?;
Expand Down
1 change: 0 additions & 1 deletion crates/fspy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

pub mod error;

#[cfg(not(target_env = "musl"))]
mod ipc;

#[cfg(unix)]
Expand Down
Loading
Loading