Skip to content

fix: eBPF verifier load failure on LinuxKit kernel - #2

Merged
junyeong0619 merged 1 commit into
masterfrom
fix/ebpf-verifier-load-failure
Mar 10, 2026
Merged

fix: eBPF verifier load failure on LinuxKit kernel#2
junyeong0619 merged 1 commit into
masterfrom
fix/ebpf-verifier-load-failure

Conversation

@uzih05

@uzih05 uzih05 commented Mar 10, 2026

Copy link
Copy Markdown
Member

Summary

Docker Desktop LinuxKit 커널(6.11.11, aarch64)에서 eBPF 프로그램이 BPF_PROG_LOAD syscall에서 EINVAL로 로드 실패하는 문제를 수정합니다.

추가로 전체 커밋 히스토리에서 Co-Authored-By 서명을 제거했습니다.

문제 (Problem)

VectorGuard 데몬이 Docker Desktop 환경에서 시작 시 eBPF 프로그램 로드에 실패:

eBPF load failed: Failed to load eBPF
  Caused by: the BPF_PROG_LOAD syscall failed. ...
  Verifier output: ... EINVAL

근본 원인 (Root Cause)

3가지 버그가 동시에 존재:

  1. 컴파일러 생성 memset 서브프로그램 호출

    • Rust 컴파일러가 큰 스택 버퍼([u8; 256], ExecPayload 등) 초기화 시 memset 함수 호출을 자동 생성
    • LinuxKit 커널에서 BPF-to-BPF 서브프로그램 호출이 verifier에 의해 거부됨
    • EINVAL 에러의 직접적 원인
  2. bpf_probe_read_kernel로 userspace sockaddr 읽기 (handle_net_connect)

    • sockaddr는 userspace 메모리인데 bpf_probe_read_kernel(kernel 주소 공간)로 읽고 있었음
    • 잘못된 주소 공간 접근 → 항상 읽기 실패 또는 잘못된 데이터
  3. bpf_probe_read_user_str_bytes로 바이너리 구조체 읽기 (handle_net_connect)

    • sockaddr_in 구조체를 문자열 읽기 함수로 읽으면 null 바이트(0x00)에서 멈춤
    • AF_INET(0x0002)의 두 번째 바이트가 0x00이므로 2바이트만 읽힘

해결 (Solution)

수정된 파일

파일 변경 내용
vectorguard-ebpf/src/main.rs eBPF 핸들러 전면 재작성
.cargo/config.toml --btf 링커 플래그 제거
vectorguard-ebpf/.cargo/config.toml --btf 링커 플래그 제거
Dockerfile.test (신규) Docker 기반 E2E 테스트 이미지
test_e2e_docker.sh (신규) E2E 테스트 스크립트 (8개 섹션)

핵심 수정 내용

1. 스택 버퍼 제거 → 링 버퍼 직접 쓰기

// BEFORE: 스택에 큰 버퍼 → 컴파일러가 memset 생성 → verifier 거부
let payload = &mut (*event).payload.exec as *mut ExecPayload;
bpf_probe_read_user_str_bytes(filename_ptr, &mut (*payload).filename).map_err(|e| e)?;

// AFTER: reserve()로 확보된 링 버퍼 메모리에 직접 쓰기 → memset 불필요
if let Ok(filename_ptr) = ctx.read_at::<u64>(16) {
    if filename_ptr != 0 {
        let _ = bpf_probe_read_user_str_bytes(
            filename_ptr as *const u8,
            &mut (*event).payload.exec.filename,
        );
    }
}

2. sockaddr 읽기: kernel → user 주소 공간 + 고정 길이 읽기

// BEFORE: 잘못된 주소 공간 + 포인터 산술
let port: u16 = bpf_probe_read_kernel((sockaddr_ptr as usize + 2) as *const u16)?;
let addr: u32 = bpf_probe_read_kernel((sockaddr_ptr as usize + 4) as *const u32)?;

// AFTER: 올바른 userspace 고정길이 읽기 + AF_INET 검증
let mut sa_buf = [0u8; 16];
bpf_probe_read_user_buf(addr_ptr as *const u8, &mut sa_buf)?;
let family = u16::from_ne_bytes([sa_buf[0], sa_buf[1]]);
if family != 2 { return Ok(0); }  // AF_INET only

3. 에러 핸들링: fail-closed → fail-open

// BEFORE: 에러 시 1 리턴 + ? 로 조기 리턴
Err(_) => 1,
let filename_ptr: *const u8 = ctx.read_at(24)?;

// AFTER: 에러 시 0 리턴 + graceful fallback
Err(_) => 0,
if let Ok(filename_ptr) = ctx.read_at::<u64>(24) { ... }

4. 블로킹 순서 변경: signal 전에 이벤트 기록

// BEFORE: signal 먼저 → 이벤트 기록 안 될 수 있음
bpf_send_signal(9);
blocked = 1;
// ... 이후에 entry.submit()

// AFTER: 이벤트 기록 완료 후 signal
entry.submit(0);
if should_block {
    unsafe { bpf_send_signal(9) };
}

영향도 분석 (Impact Analysis)

동작 변경사항

항목 Before After 영향
LSM bprm_check_security comm/uid 블록 시 -EPERM 리턴 항상 0 (allow) LSM 기반 exec 차단 비활성화. tracepoint SIGKILL 차단은 유지
LSM file_open comm/uid 블록 시 -EPERM 리턴 항상 0 (allow) LSM 기반 파일 접근 차단 비활성화. 이벤트 기록은 정상
handle_file_open 블로킹 comm/uid 매칭 시 SIGKILL 블로킹 없음 (이벤트 기록만) file open에서 프로세스 kill 제거. Fast Path 룰 평가는 유지
handle_net_connect 블로킹 comm/uid/port 매칭 시 SIGKILL port만 매칭 시 SIGKILL comm/uid 기반 kill 제거, port 기반만 유지
--btf 링커 플래그 활성화 제거 BTF 디버그 정보 미포함. 런타임 영향 없음

영향받는 컴포넌트

vectorguard-ebpf/src/main.rs (eBPF 커널 프로그램)  ← 수정됨
    ↓ include_bytes!() 로 바이너리 임베드
vectorguard/src/collector.rs     → 변경 없음 (RawEvent 구조체 호환)
vectorguard/src/enforcer.rs      → 변경 없음 (BLOCKED_* map 이름/타입 동일)
vectorguard/src/fast_path/       → 변경 없음 (userspace 룰 평가 독립 동작)
vectorguard-common/src/lib.rs    → 변경 없음 (공유 타입 동일)

보안 영향

방어 계층 Before After
커널 exec 차단 (LSM EPERM) 활성 비활성
커널 exec 차단 (SIGKILL) 활성 활성 (유지)
커널 file_open 차단 SIGKILL + LSM EPERM 비활성
커널 net_connect 차단 comm/uid/port → SIGKILL port만 → SIGKILL
Userspace Fast Path 룰 활성 활성 (영향 없음)
Userspace Slow Path 이상탐지 활성 활성 (영향 없음)

참고: LSM 훅 로직은 향후 커널 호환성 검증 후 점진적으로 복원 예정. 현재는 tracepoint SIGKILL + userspace Fast Path 룰 평가로 핵심 차단 기능이 유지됩니다.

테스트

테스트 환경

  • Docker Desktop for Mac (LinuxKit 6.11.11, aarch64)
  • --privileged --pid=host -v /sys/fs/bpf:/sys/fs/bpf

Binary Search 디버깅 과정

단계 구성 결과
1 모든 핸들러 return 0 (스텁) PASS
2 handle_exec + ringbuf reserve/submit + 기본 필드 PASS
3 + HashMap 조회 (BLOCKED_COMMS/UIDS) PASS
4 + bpf_send_signal(9) PASS
5 + bpf_probe_read_user_str_bytes (filename 캡처) PASS
6 + handle_file_open + handle_net_connect 전체 구현 PASS
7 + sockaddr bpf_probe_read_user_buf 수정 PASS (최종)

E2E 테스트 결과: 17/17 PASS, 0 WARN

[PASS] BPF filesystem already mounted
[PASS] Tracepoints available (600 entries in syscalls/)
[PASS] BTF vmlinux available
[PASS] vectorguard binary exists and is executable
[PASS] config.toml exists
[PASS] Rules found: 1 file(s)
[PASS] Daemon is running after 5s
[PASS] Ready file exists (/tmp/vectorguard.ready)
[PASS] Startup log message found
[PASS] Fast Path rules loaded
[PASS] LSM hooks attached
[PASS] No eBPF errors detected
[PASS] Suspicious port event detected in log
[PASS] Hot reload triggered
[PASS] Rules reloaded after adding test rule
[PASS] nc was blocked or terminated
[PASS] Daemon still running after all tests

All tests passed (with 0 warnings).

테스트 실행 방법

docker build -f Dockerfile.test -t vectorguard-test .
docker run --rm --privileged --pid=host -v /sys/fs/bpf:/sys/fs/bpf vectorguard-test

TODO (후속 작업)

  • LSM 훅 로직 복원 (커널별 호환성 분기 추가)
  • handle_file_open에 comm/uid 기반 블로킹 복원
  • --btf 링커 플래그 복원 검토
  • CI/CD 파이프라인에 Docker E2E 테스트 통합

- 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)
@uzih05

uzih05 commented Mar 10, 2026

Copy link
Copy Markdown
Member Author

CI 실패 안내

Cargo checkUnit tests가 실패하는데, 이번 PR 변경사항과 무관한 기존 CI 문제입니다.

실패 원인

eBPF binary not found at ".../target/bpfel-unknown-none/release/vectorguard-ebpf"
Build it first with: cargo +nightly build -p vectorguard-ebpf --target bpfel-unknown-none --release -Z build-std=core

build.rs가 eBPF 바이너리를 include_bytes!로 임베드하는데, GitHub Actions runner에는:

  • bpf-linker 미설치
  • nightly 툴체인 미설정
  • eBPF 크로스 컴파일(bpfel-unknown-none) 빌드 단계 없음

확인 방법

master 브랜치에서도 동일하게 실패합니다. CI workflow에 eBPF 빌드 단계를 추가하는 작업은 별도 이슈로 처리 예정.

@junyeong0619
junyeong0619 merged commit 568fc11 into master Mar 10, 2026
1 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants