TT Lab
블로그 시작하기
배우기 러닝패스 코스

리눅스 커널과 eBPF

검증기는 모든 갈림길을 걸어 본다

TT Lab 에서 이어서 보기

한 줄 요약

BPF 프로그램은 커널에 올라가기 전에 검증기가 모든 경로를 따라가며 안전한지 확인합니다. 거부되면 레지스터 상태가 적힌 기록만 남습니다. 그리고 올라간 프로그램은 훅에 붙어야 일을 합니다. bpftool 은 이 모든 것을 보여 주는 창입니다.

왜 검증기가 있나

BPF 프로그램은 커널 모드에서 돕니다. NULL 포인터 하나, 끝나지 않는 반복 하나가 커널 전체를 멈출 수 있습니다. 커널 모듈은 이 위험을 개발자에게 맡기지만, BPF 는 커널이 직접 막겠다고 정했습니다. 그래서 올리는 순간 검증기가 프로그램을 정적으로 분석합니다. 모든 분기의 양쪽을 따라가며 레지스터마다 지금 무엇이 들어 있는지(숫자, 컨텍스트 포인터, 맵 값 포인터, NULL 일 수 있는 포인터)와 값의 범위를 추적하고, 메모리를 건드리는 명령마다 그 접근이 안전한지 묻습니다. 하나라도 증명하지 못하면 프로그램 전체를 거부합니다.

어떻게 동작하나

가장 흔한 거부는 맵 조회 뒤의 NULL 검사 누락입니다. bpf_map_lookup_elem 은 키가 없으면 NULL 을 돌려줄 수 있습니다. 그래서 검증기는 호출 직후 R0 를 map_value_or_null 로 표시합니다. 이 상태로 R0 를 따라 메모리를 읽거나 쓰면 R0 invalid mem access 'map_value_or_null' 로 거부합니다. if (!val) return 0; 같은 분기가 있으면 검증기는 NULL 이 아닌 쪽 경로에서 R0 를 map_value 로 바꿔 기록하고 그 아래의 접근을 허용합니다. 패킷 프로그램의 경계 검사도 같은 원리입니다. data_end 와 비교해 끝을 넘지 않는다는 것이 증명된 범위만 읽을 수 있습니다.

검증기 기록은 명령 하나마다 번호: (opcode) 명령 ; 레지스터=상태 꼴로 이어지고, 거부한 줄에서 끝납니다. 원인은 대개 마지막 줄이 아니라 그보다 몇 줄 위에서 레지스터가 그 상태가 된 순간에 있습니다.

실제 기록의 한 줄을 뜯어 봅시다. 6: (85) call bpf_map_lookup_elem#1 ; R0_w=map_value_or_null(id=1,map=broken_count,ks=4,vs=8) 에서 6 은 명령 번호, 85 는 명령 코드(헬퍼 호출), 세미콜론 뒤는 이 명령을 실행한 뒤의 레지스터 상태입니다. _w 는 방금 쓰였다는 표시, id=1 은 NULL 검사로 함께 풀릴 포인터 묶음의 번호, ks·vs 는 맵의 키와 값 크기입니다. 두 줄 아래 R0 invalid mem access 가 바로 이 R0 를 따라 쓴 명령입니다.

올라간 프로그램과 맵은 커널 객체이고 각자 id 를 갖습니다. 그런데 프로그램은 링크로 훅에 붙어야 사건을 받습니다. 프로그램을 올리기만 하면 맵은 생기지만 숫자는 0 에 머뭅니다. 커널 객체는 참조가 없어지면 사라지므로, 올린 도구가 끝나도 남기려면 /sys/fs/bpf 의 BPF 파일시스템에 핀합니다. bpftool prog loadall <오브젝트> <디렉터리> autoattach 는 섹션 이름을 보고 붙인 뒤 링크를 핀합니다.

bpftool prog show·map show·link show·net show 가 각각 프로그램·맵·링크·네트워크 부착을 보여 줍니다. -j 를 붙이면 JSON 입니다. 오브젝트에 BTF 가 있으면 map dump 가 키와 값을 타입에 맞춰 풀어 줍니다.

현장에서 만나는 모습

Cilium 이나 Falco 를 올렸는데 동작하지 않는다면 먼저 bpftool prog show 로 프로그램이 올라갔는지, bpftool link show 와 bpftool net show 로 붙었는지 봅니다. "올라갔지만 안 붙은" 상태는 에이전트 로그에 오류가 없어도 생깁니다. 커널 판을 올린 뒤 에이전트가 시작을 못 하면 로그의 검증기 기록을 읽습니다. 새 커널의 검증기가 더 엄격해졌거나, 헬퍼 함수의 인자 규칙이 바뀐 경우가 많습니다.

명령 수 한도(복잡도 한도, 기본 100만 명령)도 자주 만납니다. 검증기가 따라가야 하는 경로가 너무 많아지면 프로그램이 짧아도 거부됩니다. 반복을 줄이거나 함수로 나눠 해결합니다.

English corner

다음 실습에서 할 것

이미 올라가 있는 프로그램을 JSON 으로 저장하고, 작은 C 프로그램을 BTF 와 함께 컴파일해 BTF 를 들여다봅니다. 올리면서 붙여 맵이 오르는 것을 확인하고, NULL 검사가 빠진 프로그램을 올려 검증기 기록을 저장한 뒤, 고쳐서 다시 올립니다.