fix: eBPF verifier load failure on LinuxKit kernel - #1
Closed
uzih05 wants to merge 17 commits into
Closed
Conversation
Phase 2 — Fast Path - TOML 기반 규칙 엔진 (match_process glob, path, port, uid 조건 AND 매칭) - rules/default.toml 기본 규칙 (민감파일 차단, 쉘 탐지, 의심 포트 알림) Phase 3 — Slow Path - 로컬 결정론적 64차원 특성 벡터 임베더 (OpenAI 옵션 포함) - Qdrant REST API 클라이언트 (컬렉션 자동 생성, upsert, 코사인 유사도 검색) - 유사 과거 행동 없으면 anomaly → action/severity 자동 상향 Adapter 구현 - Falco: JSON 로그 파일 tail (priority 매핑 포함) - Auditd: SYSCALL 레코드 파싱 - Tetragon: HTTP 헬스체크 스텁 (gRPC는 TODO) TUI 및 배선 - tui::run()에 mpsc::Receiver<NormalizedEvent> 연결 (tokio::select!) - 논블로킹 키 폴링으로 이벤트 수신과 UI 동시 처리 - main.rs: 수집기 → Fast Path → Slow Path → TUI 전체 파이프라인 완성 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaced all Korean text (comments, log messages, doc strings, config comments) with English equivalents across every source file. No logic changes — translation only. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
build.rs - Cross-compiles vectorguard-ebpf for bpfel-unknown-none on Linux - No-op on macOS/other hosts - collector.rs now loads eBPF binary from OUT_DIR Scope filtering (scope.rs) - ScopeFilter checks event.process.binary against config.scope.targets - Supports glob patterns (e.g. "py*", "nginx") - Empty targets list passes all events - Wired into the pipeline before Fast Path Unit tests (18 tests, all passing) - fast_path/rules: builtin rules, custom uid/glob/port rules, first-match-wins behavior - slow_path/embedder: vector dimension, unit normalization, determinism, cross-event-type differentiation - scope: empty targets, exact match, glob match - config: load config.toml Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- proto/tetragon.proto: minimal Tetragon API v1 schema covering ProcessExec, ProcessExit, ProcessKprobe, ProcessTracepoint events - build.rs: compile proto via tonic-build (protoc required on Linux hosts) - adapter/tetragon.rs: full gRPC streaming client using tonic - Subscribes to exec/exit/kprobe event types - Converts Tetragon Process → ProcessInfo, Pod → K8sMeta - Kprobe heuristics map function names to FileAccess/Network/Privilege - Auto-reconnects on stream drop with 5s backoff Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… hooks - vectorguard-common: add `blocked: u8` field to RawEvent so the eBPF program can signal to userspace that a kernel action was already taken - vectorguard-ebpf: add three eBPF HashMaps (BLOCKED_COMMS, BLOCKED_PORTS, BLOCKED_UIDS) writable by userspace; each tracepoint checks these maps and calls bpf_send_signal(SIGKILL) on match; add LSM BPF hooks (bprm_check_security, file_open) that return -EPERM proactively before the syscall completes — requires CONFIG_BPF_LSM=y, kernel ≥5.7 - vectorguard/enforcer.rs: new Linux-only Enforcer struct that owns the blocking maps taken from the Ebpf handle via take_map(); load_rules() populates maps from all fast-path rules with action=Block; block_comm/ block_uid/block_port allow real-time enforcement updates from slow path - vectorguard/collector.rs: attach_lsm() helper attaches LSM programs with graceful fallback warning if kernel lacks BPF_LSM; parse_raw_event sets Action::Blocked when raw.blocked != 0 - vectorguard/main.rs: on NativeEbpf backend, initialize Enforcer from the loaded Ebpf handle and populate it with fast-path block rules before starting the ring-buffer polling loop Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…p update
B — Slow Path context aggregation:
- New slow_path/context.rs: ContextWindow accumulates recent event vectors
per PID within a configurable time window (time_window_secs from config).
On each event, the current vector is blended (α=0.7) with the PID's
recency-weighted behavioral history before Qdrant search — reduces false
positives from isolated one-off events and gives the anomaly detector
richer behavioral signal. 4 unit tests included.
- slow_path/mod.rs: integrates ContextWindow with Mutex for interior
mutability; context vector is read before the current event is pushed
so the event does not pollute its own search baseline.
C — Hot reload sync:
- hotreload.rs now accepts a watch::Sender<Config> and broadcasts the
new config on every successful reload — pipeline components rebuild
without restarting the daemon.
- main.rs: creates watch::channel, passes Sender to hotreload task and
Receiver to run_pipeline. Pipeline uses tokio::select! to interleave
event processing with config-change notifications, then rebuilds
ScopeFilter, FastPath, and SlowPath in-place.
D — eBPF map offloading on hot reload:
- Shared Arc<Mutex<Option<Enforcer>>> accessible from both the collector
task (initializer) and run_pipeline (updater).
- On every hot-reload config change, run_pipeline calls
enforcer.load_rules(fast_path.rules()) to repopulate BLOCKED_COMMS /
BLOCKED_PORTS / BLOCKED_UIDS in the kernel — new block rules take
effect immediately without a process restart.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
K8s Namespace Filtering (scope.rs):
- ScopeFilter now implements 4-layer filtering:
1. Process binary glob (existing)
2. include_namespaces: K8s namespace allowlist (glob, empty = all pass)
3. exclude_namespaces: K8s namespace denylist (glob, takes precedence)
4. label_selectors: pod label key=value pairs (ALL must match)
- Non-K8s events (no K8sMeta) skip namespace/label checks entirely
- ScopeConfig extended with include_namespaces and label_selectors fields
- config.toml updated: default excludes kube-system, kube-public, kube-node-lease
- 12 unit tests covering all filter layers and edge cases
DaemonSet Deployment (deploy/k8s/):
- namespace.yaml: vectorguard namespace
- rbac.yaml: ServiceAccount, ClusterRole (pods/namespaces/nodes read),
ClusterRoleBinding
- configmap.yaml: config.toml + default.toml rules as ConfigMap data
- daemonset.yaml: DaemonSet with hostPID, hostNetwork, capability-based
security (SYS_ADMIN, BPF, PERFMON, NET_ADMIN, SYS_PTRACE), init
container for BPF filesystem mount, hostPath volumes for /sys/fs/bpf,
/sys/kernel/debug, /proc; liveness probe; rolling update strategy
- qdrant.yaml: StatefulSet + headless Service for Slow Path vector DB
Helm Chart (deploy/helm/vectorguard/):
- Chart.yaml, values.yaml: full parameterization of config, resources,
namespace filters, adapter backend, Qdrant toggle
- templates/: _helpers.tpl, serviceaccount, rbac, configmap, daemonset,
qdrant — all driven by values.yaml; configmap checksum annotation
for automatic pod restart on config change
- Qdrant URL templated: uses in-cluster service or external URL
CI/CD (.github/workflows/ci.yml):
- check job: cargo check on every PR
- test job: cargo test (31 unit tests)
- docker job: builds and pushes to ghcr.io on master merge
Dockerfile: updated to English, adds rules/ copy, adds protoc install
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
install.sh — bare-metal Linux installer:
- Detects distro (Ubuntu/Debian/RHEL/Fedora/Arch) and installs system
deps (clang, llvm, libelf, protobuf-compiler, linux-headers)
- Installs rustup + bpfel-unknown-none target + rust-src if missing
- Clones repo or uses local source directory
- Builds eBPF kernel program then userspace daemon
- Installs binary to /usr/local/bin, config to /etc/vectorguard
- Optionally starts Qdrant via Docker (--no-qdrant to skip)
- Creates and enables a systemd service with correct capabilities
(CAP_SYS_ADMIN, CAP_BPF, CAP_PERFMON, CAP_NET_ADMIN, CAP_SYS_PTRACE)
- Options: --no-qdrant, --no-service, --config FILE
deploy-k8s.sh — Kubernetes deployer:
- Auto-detects Helm or falls back to raw kubectl manifests
- Supports --include-ns / --exclude-ns for namespace scope at deploy time
- --adapter flag selects backend (tetragon|falco|auditd|native_ebpf)
- --dry-run, --uninstall, --image for custom images
- Waits for DaemonSet rollout and prints useful follow-up commands
uninstall.sh — bare-metal cleanup:
- Stops and disables systemd service
- Removes binary, systemd unit
- Prompts before deleting config/rules (preserves user customizations)
- Prompts before removing Qdrant Docker container + volume
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
main.rs:
- Add --config / -c <path> CLI argument parsing (std::env::args, no extra deps)
- Default config path remains "config.toml" when flag is omitted
- Write /tmp/vectorguard.ready after all components start (K8s liveness probe)
- Remove ready file on clean exit
collector.rs:
- Bind Btf to a local variable before passing &btf to program.load()
(fixes potential temporary lifetime issue on strict aya builds)
docker-compose.yml:
- Translate remaining Korean comments to English
- Add rules volume mount
- Pass --config flag to container command
- Add Qdrant healthcheck so vectorguard waits for it to be ready
- Pin Qdrant to v1.9.0 for reproducibility
install.sh:
- Fix systemd ExecStart: $CONFIG_DIR/config.toml was quoted with single
quotes inside an unquoted heredoc, preventing variable expansion
→ path was written literally instead of as /etc/vectorguard/config.toml
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add README.md: features, architecture, quick start (Docker/bare-metal/K8s), config reference, fast path rule DSL, adapter docs, slow path explanation, dev setup, and project structure - Fix Docker build: correct bpfel-unknown-none target flags, install bpf-linker, upgrade base to rust:latest + debian:trixie-slim (glibc match) - Rewrite build.rs: drop aya_build (path-conflict bug), copy pre-built eBPF binary directly from target/bpfel-unknown-none/release/ to OUT_DIR - Fix eBPF: update bpf_get_current_comm() to aya-ebpf 0.1.1 zero-arg API - Fix main.rs: resolve partial move of cfg_snap into async closure - Add headless TUI fallback (ENXIO → log-only mode for Docker/systemd) - Fix compiler warnings: remove unused BytesMut import, unused serde import - Update REPORT.md with Docker, K8s filtering, and deployment sections Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Step-by-step test instructions for Ubuntu 22.04: kernel/BPF prereq checks, install, per-feature test cases (fast path, eBPF block, hot reload, slow path/Qdrant, TUI), rule customization examples, and troubleshooting guide. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bugs fixed: - Remove invalid 'rustup target add bpfel-unknown-none' (no prebuilt std, silently failed and misled the user) - Add nightly toolchain install with rust-src (was missing entirely) - Add bpf-linker install step (was missing entirely) - Fix eBPF build command: 'cargo +nightly' instead of plain 'cargo' (-Z build-std=core is nightly-only; plain cargo used stable and failed) - Export PATH immediately after rustup install so subsequent commands work in the same shell process without needing to restart - Detect systemd via pid-1 check rather than just 'command -v systemctl' (WSL has systemctl binary but may not run systemd as init) - Show manual run instructions + WSL systemd enable steps when no systemd Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
openssl-sys crate requires libssl-dev (Debian/Ubuntu), openssl-devel (RHEL/Fedora), or openssl (Arch) to compile. Missing package caused userspace daemon build to fail with openssl-sys error. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- fix(main): run_pipeline stays alive in degraded mode when eBPF/adapter fails — parks the event arm via Option<Receiver> instead of breaking - fix(tui): headless mode no longer exits when event pipeline closes; parks to sleep instead of exiting the daemon - fix(build.rs): add eBPF binary to cargo:rerun-if-changed so daemon re-embeds updated binary automatically - fix(ebpf): enable BTF generation via bpf-linker --btf flag in .cargo/config.toml for both package and workspace root - fix(ebpf): set lto = false to avoid bpf-linker LTO issues - fix(install): add libprotobuf-dev / protobuf-devel for well-known proto types; add libssl-dev / openssl-devel for openssl-sys - feat: bundle google/protobuf well-known types (timestamp, wrappers) so build works without libprotobuf-dev system package - feat: add test_ubuntu.sh integration test script covering daemon lifecycle, hot-reload, eBPF rules, and block enforcement - chore: add workspace .cargo/config.toml with bpf-linker settings Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- rewrite tracepoint handlers to avoid compiler-generated memset - fix sockaddr read: bpf_probe_read_kernel -> bpf_probe_read_user_buf - stub LSM hooks to ensure verifier acceptance on all kernels - add Docker-based E2E test infrastructure (Dockerfile.test, test_e2e_docker.sh) - remove --btf linker flag from cargo config (not needed for runtime)
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
Docker Desktop LinuxKit 커널(6.11.11, aarch64)에서 eBPF 프로그램이
BPF_PROG_LOADsyscall에서EINVAL로 로드 실패하는 문제를 수정합니다.문제 (Problem)
VectorGuard 데몬이 Docker Desktop 환경에서 시작 시 eBPF 프로그램 로드에 실패:
근본 원인 (Root Cause)
3가지 버그가 동시에 존재:
컴파일러 생성
memset서브프로그램 호출[u8; 256],ExecPayload등) 초기화 시memset함수 호출을 자동 생성EINVAL에러의 직접적 원인bpf_probe_read_kernel로 userspace sockaddr 읽기 (handle_net_connect)sockaddr는 userspace 메모리인데bpf_probe_read_kernel(kernel 주소 공간)로 읽고 있었음bpf_probe_read_user_str_bytes로 바이너리 구조체 읽기 (handle_net_connect)sockaddr_in구조체를 문자열 읽기 함수로 읽으면 null 바이트(0x00)에서 멈춤AF_INET(0x0002)의 두 번째 바이트가 0x00이므로 2바이트만 읽힘해결 (Solution)
수정된 파일
vectorguard-ebpf/src/main.rs.cargo/config.toml--btf링커 플래그 제거vectorguard-ebpf/.cargo/config.toml--btf링커 플래그 제거Dockerfile.test(신규)test_e2e_docker.sh(신규)핵심 수정 내용
1. 스택 버퍼 제거 → 링 버퍼 직접 쓰기
2. sockaddr 읽기: kernel → user 주소 공간 + 고정 길이 읽기
3. 에러 핸들링: fail-closed → fail-open
4. 블로킹 순서 변경: signal 전에 이벤트 기록
영향도 분석 (Impact Analysis)
동작 변경사항
bprm_check_security-EPERM리턴0(allow)bpf_send_signal(9)차단은 유지file_open-EPERM리턴0(allow)handle_file_open블로킹SIGKILLhandle_net_connect블로킹SIGKILLSIGKILL--btf링커 플래그bpftool조회 시 타입 정보 없음. 런타임 영향 없음영향받는 컴포넌트
collector.rs: 변경 없음.load_ebpf(),run_collector(),parse_raw_event()모두 기존RawEvent구조체와 호환enforcer.rs: 변경 없음.BLOCKED_COMMS/PORTS/UIDSmap 이름과 타입 동일. map에 값을 쓰면 eBPF 핸들러에서 읽음fast_path/rules.rs: 변경 없음. userspace 룰 평가는 eBPF 이벤트 수신 후 독립적으로 동작vectorguard-common: 변경 없음.RawEvent,EventKind,EventPayload구조체 동일보안 영향
테스트
테스트 환경
--privileged --pid=host -v /sys/fs/bpf:/sys/fs/bpfBinary Search 디버깅 과정
return 0(스텁)BLOCKED_COMMS/UIDS)bpf_send_signal(9)bpf_probe_read_user_str_bytes(filename 캡처)bpf_probe_read_user_buf수정E2E 테스트 결과 (17/17 PASS, 0 WARN)
이벤트 캡처 확인
실제 캡처된 이벤트 예시:
테스트 실행 방법
TODO (후속 작업)
handle_file_open에 comm/uid 기반 블로킹 복원--btf링커 플래그 복원 검토