research(fspy): evaluate SIGSYS interception #20
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
| 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/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 | |
| - 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 '"success":false' 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 | |
| ' |