Skip to content

Commit 5e98c71

Browse files
bmiddhaCopilot
andcommitted
Rewrite fstrace on eBPF (Rust + Aya) with full feature parity
Replace the C/ptrace implementation with an eBPF tracer written in Rust on top of Aya. Tracing now happens in-kernel via fexit probes on the traced syscalls plus sched_process_{fork,exec,exit} tracepoints and a ring buffer, instead of stopping the target on every syscall from userspace. No features are dropped: full syscall coverage, path resolution (cwd/dirfd/AT_FDCWD + normalization), the fd->path cache, process-tree scoping, and exit/signal/ env passthrough are all preserved, alongside filters, debounce, and per-process logging. Architecture (privileged/unprivileged split) - fstrace-daemon: the privileged half. Loads and attaches the embedded eBPF object, owns the ring buffer and the traced-pid map, and serves multiple clients over a Unix socket. It routes events purely by root-namespace pid, so it works correctly from inside its own pid+mount namespace and needs no --pid=host. When tracefs is absent (e.g. a scratch container) it mounts tracefs itself before loading. - fstrace: the unprivileged client. Runs as the invoking user with no sudo, forks the target, registers it with the daemon, and streams reports on fd 3. Exit code, signals, and environment are passed through untouched. - fstrace-common: shared #[repr(C)] POD event structs and typed enums (Syscall/AccessType/FileType) used by both the eBPF and userspace sides. - fstrace-ebpf: the eBPF programs (fexit per traced syscall + fork/exec/exit tracepoints writing into a ring buffer). The fork tracepoint's child_pid offset is read from tracefs at load time rather than hardcoded, tracking the ~6.16 __data_loc layout change. Packaging & deployment - systemd service + socket units (socket-activation friendly), a SysV init.d script, and man pages for both binaries. - A FROM-scratch Dockerfile for the fully static (musl, static-PIE) daemon; bind-mount the socket dir + /sys/kernel/btf and run --privileged to trace host commands across namespaces. Rootful runtime required (rootless cannot create BPF maps). - npm distribution package retained under node/fstrace. Tooling & lifecycle (xtask) - The entire repo lifecycle runs through `cargo xtask`: fmt, lint, test, check, build, clean, dist (cross-compile), docker-build/docker-test, and vm-test. All former shell scripts are ported to Rust. - Cross-compilation for {x86_64, aarch64} x {gnu, musl} produces release tarballs. Testing - Host-side unit tests cover path normalization, filters, debounce, classification, and resolution. - The privileged loader and full syscall coverage run end-to-end inside a throwaway QEMU VM so eBPF never loads on the host. The harness (all Rust) downloads prebuilt upstream kernels from the Ubuntu mainline archive, builds an initramfs from a Docker image rootfs (Ubuntu and Alpine/musl), and direct-boots QEMU, parsing a console result sentinel. A thin fstrace-scenario binary issues the raw syscalls under trace. Coverage includes 23 syscall/behavioural tests (incl. near-PATH_MAX paths, multi-client isolation, passthrough), namespace isolation with a containerized daemon, and systemd socket-activation. Kernel support - Verified across LTS {5.10, 5.15, 6.1, 6.6, 6.12, 6.18}, stable 7.1, and mainline 7.2-rc. Feature floor is the ring buffer (5.8); 5.4 and older lack it. Requirements and the support matrix are documented in docs/kernel-support.md. Docs - README plus focused docs/ pages: architecture, building, testing, deployment, reports-and-fd3, and kernel-support. CI - Three jobs, all driven by the cargo xtask alias: format/clippy/host tests, a vm-e2e matrix (8 kernels x {ubuntu, alpine}), and a cross-dist matrix (4 targets) uploading release tarballs. - A reusable kernel-maintenance agent workflow plus a weekly scheduled pipeline that delegates kernel-matrix bumps to the Copilot coding agent. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent a7a2b40 commit 5e98c71

71 files changed

Lines changed: 9174 additions & 3497 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.cargo/config.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Cross-compilation linkers for the supported release targets.
2+
#
3+
# aarch64 builds need a cross linker; install it with:
4+
# apt-get install -y gcc-aarch64-linux-gnu
5+
#
6+
# x86_64-unknown-linux-musl links self-contained via rust-lld and needs no
7+
# extra toolchain. See `cargo xtask dist` and docs/building.md.
8+
9+
[alias]
10+
xtask = "run --package xtask --"
11+
12+
[target.aarch64-unknown-linux-gnu]
13+
linker = "aarch64-linux-gnu-gcc"
14+
15+
[target.aarch64-unknown-linux-musl]
16+
linker = "aarch64-linux-gnu-gcc"

.clang-format

Lines changed: 0 additions & 7 deletions
This file was deleted.

.devcontainer/devcontainer.json

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
{
2-
"image": "mcr.microsoft.com/devcontainers/cpp:1-ubuntu-24.04",
2+
"image": "mcr.microsoft.com/devcontainers/rust:1-bookworm",
33
"features": {
44
"ghcr.io/devcontainers/features/node:2": {
5-
"nodeGypDependencies": true,
6-
"version": "18",
7-
"pnpmVersion": "latest",
8-
"nvmVersion": "latest"
5+
"version": "18"
96
}
107
},
8+
"onCreateCommand": "rustup show && rustup component add rust-src clippy rustfmt && cargo install bpf-linker --locked",
119
"customizations": {
1210
"vscode": {
1311
"extensions": [
14-
"ms-vscode.cmake-tools",
15-
"ms-vscode.cpptools"
12+
"rust-lang.rust-analyzer",
13+
"vadimcn.vscode-lldb"
1614
]
1715
}
1816
}
19-
}
17+
}

.github/workflows/ci.yml

Lines changed: 96 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,102 @@ name: ci
33
on: [push, pull_request]
44

55
jobs:
6-
cmake-release:
7-
name: CMake Build and Test
6+
lint-and-unit:
7+
name: Format, Clippy & Host Unit Tests
88
runs-on: ubuntu-latest
9+
steps:
10+
- name: 📥 Checkout code
11+
uses: actions/checkout@v7
12+
13+
- name: 🦀 Install Rust toolchain
14+
run: |
15+
rustup show
16+
rustup component add rustfmt clippy rust-src
17+
cargo install bpf-linker --locked
18+
19+
- name: 💄 Format check
20+
run: cargo xtask fmt --check
21+
22+
- name: 📎 Clippy
23+
run: cargo xtask lint
24+
25+
- name: 🔨 Build (release)
26+
run: cargo xtask build
27+
28+
- name: 🧪 Host unit tests
29+
run: cargo xtask test
30+
31+
vm-e2e:
32+
name: VM e2e (${{ matrix.userspace.name }} on ${{ matrix.release }})
33+
runs-on: ubuntu-latest
34+
needs: lint-and-unit
35+
strategy:
36+
fail-fast: false
37+
matrix:
38+
release: ["5.10", "5.15", "6.1", "6.6", "6.12", "6.18", "7.1", "7.2-rc"]
39+
userspace:
40+
- name: ubuntu
41+
image: docker.io/library/ubuntu:latest
42+
- name: alpine
43+
image: docker.io/library/alpine:latest
44+
steps:
45+
- name: 📥 Checkout code
46+
uses: actions/checkout@v7
947

48+
- name: 🦀 Install Rust toolchain
49+
run: |
50+
rustup show
51+
rustup component add rust-src
52+
rustup target add x86_64-unknown-linux-musl
53+
cargo install bpf-linker --locked
54+
55+
- name: 💿 Install QEMU & tooling
56+
run: |
57+
sudo apt-get update
58+
sudo apt-get install -y qemu-system-x86 cpio binutils curl
59+
60+
- name: ⚡ Enable KVM (best effort)
61+
run: sudo chmod 666 /dev/kvm || true
62+
63+
- name: 🖥️ Run VM e2e suite
64+
env:
65+
FSTRACE_VM_IMAGE: ${{ matrix.userspace.image }}
66+
run: cargo xtask vm-test --release ${{ matrix.release }}
67+
68+
cross-dist:
69+
name: Cross-Compile Release Artifacts
70+
runs-on: ubuntu-latest
71+
needs: lint-and-unit
72+
strategy:
73+
fail-fast: false
74+
matrix:
75+
target:
76+
- x86_64-unknown-linux-gnu
77+
- x86_64-unknown-linux-musl
78+
- aarch64-unknown-linux-gnu
79+
- aarch64-unknown-linux-musl
1080
steps:
11-
- name: Checkout code
12-
uses: actions/checkout@v4
13-
14-
- name: Build Project (Release)
15-
uses: threeal/cmake-action@v2.1.0
16-
with:
17-
options: |
18-
CMAKE_BUILD_TYPE=Release
19-
20-
- name: Test Project (Release)
21-
uses: threeal/ctest-action@v1.1.0
22-
23-
- name: Build Project (Debug)
24-
uses: threeal/cmake-action@v2.1.0
25-
with:
26-
options: |
27-
CMAKE_BUILD_TYPE=Debug
28-
29-
- name: Test Project (Debug)
30-
uses: threeal/ctest-action@v1.1.0
81+
- name: 📥 Checkout code
82+
uses: actions/checkout@v7
83+
84+
- name: 🦀 Install Rust toolchain
85+
run: |
86+
rustup show
87+
rustup component add rust-src
88+
rustup target add ${{ matrix.target }}
89+
cargo install bpf-linker --locked
90+
91+
- name: 🔗 Install aarch64 cross linker
92+
if: startsWith(matrix.target, 'aarch64')
93+
run: |
94+
sudo apt-get update
95+
sudo apt-get install -y gcc-aarch64-linux-gnu
96+
97+
- name: 📦 Build release tarball
98+
run: cargo xtask dist --target ${{ matrix.target }}
99+
100+
- name: ⬆️ Upload artifact
101+
uses: actions/upload-artifact@v7
102+
with:
103+
name: fstrace-${{ matrix.target }}
104+
path: dist/*.tar.gz
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Pipeline: run the reusable kernel-maintenance agent workflow on a schedule.
2+
#
3+
# Upstream cuts a new mainline -rc most Sundays and ships stable/longterm point
4+
# releases every week or two, so a weekly check keeps the tested matrix current.
5+
# The reusable workflow is idempotent: it skips itself when a kernel-maintenance
6+
# issue is already open, so overlapping runs never stack up duplicate tasks.
7+
#
8+
# Runs can also be started manually from the Actions tab (workflow_dispatch),
9+
# including a dry run that files the task issue without assigning the agent.
10+
name: kernel-maintenance-cron
11+
12+
on:
13+
schedule:
14+
# 06:00 UTC every Monday.
15+
- cron: "0 6 * * 1"
16+
workflow_dispatch:
17+
inputs:
18+
dry_run:
19+
description: "Compose the task issue but do not assign the Copilot agent."
20+
type: boolean
21+
default: false
22+
23+
permissions:
24+
contents: read
25+
issues: write
26+
27+
jobs:
28+
maintain:
29+
name: 🗓️ Scheduled kernel maintenance
30+
uses: ./.github/workflows/kernel-maintenance.yml
31+
with:
32+
dry_run: ${{ inputs.dry_run == true }}
33+
secrets: inherit

0 commit comments

Comments
 (0)