Post

eBPF

eBPF

1. 개요

원래는 eBPF는 확장된 BPF(extended BPF)라는 뜻이었다. 여기서 BPF는 Berkeley Packet Filter의 약자로 원래는 운영 체제 수준에서 컴퓨터 네트워크 패킷을 캡처하고 필터링 할 수 있는 네트워크 탭 및 패킷 필터였으나 이를 구현하기 위해서 리눅스 커널 같은 특권 컨텍스트 안에서 코드를 실행시키는 기술이 도입되었다. 이 과정에서 해당 기술의 가능성을 눈여겨본 여러 개발자들이 이를 확장하였고 이 기술은 eBPF라는 “샌드박스된 프로그램”을 안전하고 고성능으로 실행하게 해 주는 커널 기술이 되었다. 이 기술은 커널 소스 코드를 변경하거나 커널 모듈을 로드할 필요 없이 커널의 기능을 안전하고 효율적으로 확장할 수 있도록 도와준다.

원래는 리눅스 커널에서 유래된 기술이었으나, 이는 2021년에 Windows에서도 적용되었다.

2. 구조

eBPF를 구성하는 컴포넌트들은 아래와 같다.

1) Overview

| 구성요소 | 위치 | 역할 요약 | | ——————- | —– | —————————- | | eBPF 프로그램(바이트코드) | 커널 | 훅에서 실행되는 로직(필터링, 관측, 정책 등) | | eBPF 맵(Maps) | 커널 | 상태 저장 및 유저·커널 간 데이터 공유 | | Helper 함수 | 커널 | eBPF에서 호출 가능한 제한된 커널 API | | Verifier | 커널 | 프로그램 로드시 정적 분석·안전성 검증 | | Interpreter/JIT | 커널 | 바이트코드 실행(인터프리트 또는 기계어 JIT) | | Hook / Program Type | 커널 | 어떤 이벤트에 어떤 ABI로 실행되는지 정의 | | bpf() syscall | 커널/유저 | 프로그램·맵·링크를 커널에 로드/관리하는 인터페이스 | | Loader(libbbf 등) | 유저 공간 | eBPF 오브젝트(.o) 로드·attach·맵 관리 |

2) eBPF 프로그램

실질적으로 실행되는 로직을 담은 코드이다. C와 같이 eBPF 바이트 코드를 짤 수 있는 언어로 구현이 되면 컴파일러를 거쳐 LINUX에서는 ELF 파일로 직렬화된다.

3) eBPF 맵

eBPF 맵은 커널에 존재하는 데이터 구조이다. eBPF 프로그램과 사용자 공간 프로그램 모두 이러한 맵에 접근할 수 있으므로, 맵은 eBPF 프로그램과 사용자 공간 간의 통신 계층 역할을 할 뿐만 아니라 프로그램 호출 간에 데이터를 유지하는 저장소로도 사용된다. 다른 모든 BPF 객체와 마찬가지로 맵은 호스트 전체에서 공유되며, 여러 프로그램이 동시에 동일한 맵에 접근할 수 있다. 따라서 맵은 서로 다른 연결 지점에 있는 서로 다른 유형의 프로그램 간에 정보를 전송하는 데에도 사용할 수 있다.

4) Helper

기본적으로 eBPF 프로그램 자체는 상당히 제한적인데, 할 수 있는거라고는 로컬 스택에서 읽고 쓰기, 레지스터에 대한 연산, 내부 함수 호출, 조건부 점프 등이 있다. 이 모든 것은 프로그램 자체의 작은 영역 내에서 이루어진다. 프로그램이 할 수 있는 마지막 작업은 소위 “헬퍼 함수”를 호출하는 것으로 이 헬퍼 함수는 실제로 커널에 정의된 일반 C 함수이다. 이 함수들은 eBPF 프로그램과 커널 간의 내부 API/ABI 역할을 하며, 이러한 헬퍼 함수를 통해 eBPF 프로그램은 검증기를 통과할 수 없어서 수행할 수 없었던 작업을 수행할 수 있다. 헬퍼 함수는 최대 5개의 인수를 받고 하나의 반환 값을 반환한다.

5) Verifier

Verifier는 eBPF 아키텍처의 핵심 안전성 컴포넌트이다. 프로그램 로드 시점에 정적 분석으로 아래의 내용에 대해서 검증한다.

  • 메모리 안전
    • 스택/맵/패킷 버퍼 접근이 범위를 벗어나지 않는지
    • 포인터 연산 규칙을 지키는지(검증된 포인터만 dereference 등)
  • 종료 보장
    • 루프에 상한이 있는지, 무한 루프가 아닌지
  • 레지스터 상태 추적
    • 미초기화 레지스터 사용 방지
    • 각 레지스터 타입(스칼라 vs 포인터 vs 맵 value 등)을 추적
  • Helper 인자 검증
    • helper 함수에 전달되는 인자 타입·범위 검사
  • 커널 정보 누출과 같은 보안 속성
    • 커널 주소를 유저 공간으로 누출하지 않는지 등

Verifier가 제대로 작동하지 않으면 보안적으로 매우 위험해질 수 있기 때문에 꽤나 strict하게 검증하는 편이며 이 때문에 eBPF를 이용한 코딩은 꽤나 어렵다고 한다.

6) Hook

Hook은 커널이 어떤 이벤트를 처리하는 도중에, 외부 코드(eBPF 프로그램)를 불러줄 수 있도록 미리 약속해 둔 지점이라고 볼 수 있다.
아래와 같은 Hook에 붙어서 호출될 수 있다.

  • 네트워크: XDP, TC ingress/egress, socket filter, cgroup hook 등
  • 트레이싱: kprobe, kretprobe, tracepoint, fentry/fexit, uprobe 등
  • 보안: LSM hook(e.g. 파일 접근, 소켓 연결 허용/차단 등)
  • 기타: cgroup, 스케줄러, perf events 등

7) bpf syscall

유저 공간 프로그램(로더, bpftool, bpftrace 등)이 커널의 eBPF 서브시스템을 조작하기 위한 단일 진입점(entry point)”인 시스템 콜이다. eBPF 관련 모든 오퍼레이션의 진입점이기도 하다. bpf()의 cmd 값에 따라 “오브젝트 생성/로드”, “맵 접근”, “링크/핀 관리” 같은 작업을 수행한다. bpf syscall이 ebpf를 호출하여 실행하다가 verifier가 invalid하다고 판별하면 error를 반환한다.

8) Loader

컴파일된 eBPF 오브젝트(보통 ELF .o 파일)를 읽어서 bpf() 시스템 콜로 맵을 만들고(BPF_MAP_CREATE), 프로그램을 커널에 로드(BPF_PROG_LOAD)해서 verifier/JIT 단계를 거치게 한 뒤 로드된 프로그램을 특정 hook 지점에 attach(예: kprobe/tracepoint/XDP 등)하고, 필요하면 bpffs(/sys/fs/bpf)에 pin해서 수명을 관리한다.

3. 사용 예시

1) 예시 1: 프로세스가 파일을 열 때(open) 로그 남기기 (트레이싱)

파일 open 관련 커널 훅(예: tracepoint/kprobe)에 eBPF 프로그램을 attach해서, 이벤트가 발생할 때마다 PID/커맨드/파일 경로 등을 수집해 유저 공간으로 전달한다. 이때 수집 데이터는 eBPF map(예: ring buffer)에 넣고, 유저 공간 프로그램이 map에서 읽어 출력하는 패턴이 흔하다.

2) 예시 2: 특정 UDP 트래픽 차단 (XDP)

XDP 훅에 eBPF 프로그램을 붙이면 NIC 드라이버 레벨에서 패킷을 매우 이른 시점에 검사해(예: IPv4 UDP면 drop) 나머지 트래픽은 그대로 커널 네트워크 스택으로 통과시키는 구성이 가능하다.

참고문헌

This post is licensed under CC BY 4.0 by the author.