Skip to content

research(fspy): evaluate SIGSYS interception #19

research(fspy): evaluate SIGSYS interception

research(fspy): evaluate SIGSYS interception #19

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: Sandboxed Vitest browser under transient ptrace
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: Run sandboxed Vitest browser mode 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/fixture"
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/fixture"
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
if test "$run_status" -ne 0; then
sudo dmesg | tail -200 || true
fi
test "$run_status" -eq 0
test -s dist/result.json
grep -F '"success":true' dist/result.json
grep -F 'chromiumSandbox: true' "$probe_dir/bridge.log"
test ! -s "$no_sandbox_record"
test -s "$sandbox_record"
grep -F $'NoNewPrivs:\t1' "$sandbox_record"
grep -F $'Seccomp:\t2' "$sandbox_record"
grep -E $'Seccomp_filters:\t([2-9]|[1-9][0-9]+)' "$sandbox_record"
cat "$sandbox_record"
grep -E 'bridge: injected exec .*exe=.*/(chrome|headless_shell)' \
"$probe_dir/bridge.log"
grep -E 'bridge: summary injected_execs=[2-9][0-9]* failed_execs=' \
"$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
'