Загружаем каталог…
Загружаем каталог…
컴파일된 eBPF 오브젝트 파일( .o )에 미리 서명해 두면, 커널은 로드할 때 그 서명을 확인할 수 있을까. 2022년 말에 쓰인 책의 답은 "쉽지 않다"였다. 이유는 암호학이 아니었다. 사용자 공간 로더가 map 위치와 CO-RE 정보로 프로그램을 고친 뒤에 커널에 올리기 때문이다. 책은 이것이 서명 관점에서는 악의적 수정과 구분하기 어렵다고 썼다. 9주차 끝에서 "언어별 라이브러리는 커널 쪽 보장을 얼마나 드러내는가"를 물었다. 10장이 소개하는 라이브러리들을 열어 보면 공통으로 하는 일이 하나 있다. 바로 이 "고쳐 쓰기"다. 그리고 11장이 미해결로 남긴 서명 문제는 2025년 커널 6.18에서, 고쳐 쓰기를 커널 안으로 옮기는 방식으로 풀렸다. 이 글을 읽고 나면 다음을 설명할 수 있어야 한다. bpftrace는 책 설명과 달리 지금 무엇 위에서 도는가 로더는 BPF_PROG_LOAD 직전에 바이트코드의 어느 필드에 무엇을 써 넣는가 ebpf-go, Aya, libbpfgo, libbpf-rs는 그 고쳐 쓰기를 누가 구현하는가 책이 소개한 라이브러리와 예제 코드는 2026년에 어떤 상태인가 서명은 왜 어려웠고, 6.18은 그 문제를 어떤 구조로 풀었는가 책의 나머지 예측(kptr, 메모리 할당, Windows, 표준화)은 어떻게 됐는가 이 글에서 "2026년 현재"는 조사 시점에 고정한 Linux v7.2와 각 출처의 조사 결과를 뜻한다. v7.2 이후의 개발 중인 변경은 이 글의 현재 판정에 포함하지 않는다. 1절은 가장 높은 추상화인 bpftrace에서 출발한다. 2 3절은 로더가 하는 일을 실제 바이트 수준으로 본 뒤 라이브러리별 구현 위치를 비교하고, 4 5절은 책의 목록과 예제를 현재 기준으로 채점한다. 6 7절은 2절의 고쳐 쓰기가 서명을 어떻게 막았고 어떻게 풀렸는지를 실측과 함께 따라가며, 8 9절은 나머지 예측과 책 이후의 변화를 정리한다. 1. bpftrace는 무엇을 감추고, 지금은 무엇 위에서 도는가 책은 eBPF 프로그래밍을 두 부분으로 나눈다. 커널에서 도는 eBPF 프로그램, 그리고 그 프로그램을 관리하고 상호작용하는 사용자 공간 코드다. bpftrace는 이 구분을 사용자에게서 감추는 가장 높은 추상화다(p.185). 책은 bpftrace 절 끝에서 bpftrace가 BCC 위에 만들어졌고, 스크립트가 BCC 프로그램으로 변환된 뒤 런타임에 LLVM/Clang으로 컴파일된다고 쓴다(p.188). 이 설명은 책이 출간되기 전에 이미 바뀌어 있었다. bpftrace는 2022년 6월 PR #2265("Move from bcc to libbpf")로 BCC 대신 libbpf를 쓰기 시작했고, 이 변경은 2022년 8월 v0.16.0에 들어갔다. 전환은 한 번에 끝나지 않았다. 프로그램과 map을 libbpf의 bpf_object 단위로 올리는 변경은 2024년 7월 커밋("Load BPF programs and maps via libbpf bpf_object")으로 v0.22.0에 들어갔다. 2026년 9월에 나온 v0.27.0의 README는 "LLVM을 컴파일러 백엔드로, libbpf를 BPF 서브시스템과의 상호작용에" 쓴다고 설명한다. 그렇다고 BCC를 완전히 뗀 것도 아니다. v0.27.0의 CMakeLists.txt 는 여전히 find_package(LibBcc REQUIRED) 를 요구한다. 그래서 정확한 서술은 "커널과 대화하는 부분은 libbpf가 맡고, 빌드에는 아직 libbcc가 필요하다"이다. BCC가 지금 정확히 어떤 기능에 쓰이는지는 이 글에서 코드로 확인하지 않았다. bpftrace를 쓰는 사람은 이 변화를 알아차릴 필요가 없다. 그게 bpftrace가 감추는 것이다. 감춰진 아래층, 즉 사용자 공간 로더가 실제로 무슨 일을 하는지는 다음 절에서 바이트 수준으로 본다. 2. 로더는 BPF_PROG_LOAD 직전에 무엇을 써 넣는가 아래는 이 글의 실측에 쓴 최소 프로그램이다. map 하나를 조회하고, task_struct 의 필드 하나를 CO-RE로 읽는다. /* lab/hello.bpf.c 발췌 */ struct { __uint(type, BPF_MAP_TYPE_HASH); ... } counter SEC(".maps"); SEC("tracepoint/syscalls/sys_enter_execve") int hello(void *ctx) { struct task_struct *t = (void *)bpf_get_current_task(); u32 tgid = BPF_CORE_READ(t, tgid); /* CO-RE 재배치 대상 */ ... v = bpf_map_lookup_elem(&counter, &tgid); /* map fd 재배치 대상 */ 이 파일을 clang으로 컴파일한 .o 와, 로드한 뒤 커널이 보여 주는 프로그램을 같은 명령어 위치에서 비교하면 다음과 같다(커널 7.0, bpftool v7.7.0). .o (llvm-objdump) 11: r1 = 0x0 ll ; ELF 재배치 레코드: R_BPF_64_64 counter 로드 후 (bpftool xlated) 11: (18) r1 = map[id:51] 첫 줄의 r1 = 0x0 ll 은 64비트 즉치값을 레지스터에 넣는 명령어다. 값이 0인 이유는 컴파일러가 counter map이 어디 있을지 모르기 때문이다. 대신 ELF에 "이 자리는 counter 를 가리켜야 한다"는 재배치 레코드를 남긴다. 이 자리를 빈칸이라고 부르겠다. 둘째 줄은 같은 명령어가 커널 안에서 특정 map을 가리키게 바뀐 모습이다. 빈칸을 채우는 일은 두 단계로 나뉜다. 사용자 공간 로더가 빈칸에 map fd를 적고, 커널 verifier가 그 fd를 실제 map 포인터로 바꾼다. bpftool은 그 포인터를 map id로 보여 준다. libbpf에서 첫 단계를 하는 함수는 bpf_object__relocate_data() 다. /* src/libbpf.c, libbpf v1.7.0, bpf_object__relocate_data() 6367-6379행 */ case RELO_LD64: map = &obj->maps[relo->map_idx]; if (obj->gen_loader) { insn[0].src_reg = BPF_PSEUDO_MAP_IDX; insn[0].imm = relo->map_idx; } else if (map->autocreate) { insn[0].src_reg = BPF_PSEUDO_MAP_FD; insn[0].imm = map->fd; } else { ... 소스 - src/libbpf.c#L6367-L6379 일반적인 경우( else if 분기)에는 명령어의 src_reg 필드에 "이 즉치값은 map fd다"라는 표시( BPF_PSEUDO_MAP_FD )를 하고, imm 필드에 이 프로세스의 map fd 번호를 적는다. 첫 번째 if (obj->gen_loader) 분기는 7절의 light skeleton이 쓰는 경로다. 거기서는 fd 대신 map의 인덱스를 적는다. 이 차이가 서명 문제를 푸는 열쇠가 된다. map 참조만 빈칸인 것은 아니다. 같은 함수와 CO-RE 처리 함수가 채우는 빈칸을 종류별로 모으면 다음과 같다. 재배치 종류 무엇을 가리키는가 명령어에 써 넣는 값 처리 위치(libbpf v1.7.0) RELO_LD64 map src_reg=BPF_PSEUDO_MAP_FD , imm =map fd bpf_object__relocate_data() RELO_DATA 전역 변수(.data/.bss 등) src_reg=BPF_PSEUDO_MAP_VALUE , map fd + 오프셋 같은 함수 RELO_EXTERN_LD64 (typed ksym) 커널 변수 src_reg=BPF_PSEUDO_BTF_ID , 커널 BTF ID + BTF obj fd 같은 함수 RELO_EXTERN_CALL kfunc src_reg=BPF_PSEUDO_KFUNC_CALL , BTF ID + fd 배열 인덱스 같은 함수 CO-RE 구조체 필드 오프셋 등 대상 커널 BTF로 계산한 값 bpf_object__relocate_core() 의 resolve, patch 두 단계 표의 값에는 공통점이 있다. map fd, BTF obj fd, 대상 커널의 필드 오프셋은 모두 그 호스트에서 로드하는 순간에만 정해진다 . 같은 .o 를 두 번 로드해도 fd 번호가 다를 수 있고, 다른 커널에 올리면 CO-RE 오프셋이 달라질 수 있다. 위 실측에서 tgid 오프셋이 .o (0x774)와 로드 후(1908)에 같은 값인 것은, vmlinux.h 를 같은 커널에서 뽑았기 때문이다. 순서에도 작은 장치가 있다. libbpf의 bpf_object_prepare() 는 map을 만들기 전에 재배치부터 한다. resolve_externs → relocate(빈칸에 fd 기록) → BTF 로드 → create_maps → prepare_progs ↑ ↓ placeholder memfd로 fd 번호를 먼저 확보 실제 map을 같은 번호에 덮어씀(reuse_fd) map이 아직 없는데 fd를 적을 수 있는 이유는, map 객체를 만들 때 memfd로 fd 번호부터 맡아 두고 나중에 실제 map을 그 번호에 앉히기 때문이다. BPF 메인테이너 Alexei Starovoitov는 LPC 2022 발표에서 이 역할을 한 줄로 "libbpf is a linker"라고 표현했다. 함수를 붙이고 참조를 확정한다는 점에서 링커가 맞다. 다만 이 비유는 한 지점에서 깨진다. 보통 링커는 오프라인에서 결과 파일을 만들고, 그 결과 파일에 서명할 수 있다. eBPF 로더가 확정하는 값은 대상 호스트에서 로드하는 순간에만 존재한다. 그래서 사용자 공간이 빈칸을 채워 넘기는 일반 경로에서는, 확정된 결과물에 미리 서명해 둘 방법이 없다. 6절에서 이 지점으로 돌아온다. 같은 발표는 CO-RE에 대해서도 선을 긋는다. CO-RE, kconfig, 타입 정보로 이식성을 얻지만 "It's not guaranteed"이고, 이식성을 유지하려면 프로그램을 고쳐야 할 수 있다고 했다. CO-RE는 필드 오프셋 차이를 로드 시점에 맞춰 줄 뿐, 대상 커널에 없는 helper나 kfunc, 프로그램 타입까지 만들어 주지는 않는다. 3. 같은 빈칸, 누가 채우는가: 위임과 재구현 책도 라이브러리를 두 갈래로 나눴다. ebpf-go는 CO-RE 지원을 포함해 "purely in Go"로 구현됐고, Aya는 libbpf에 의존하지 않고 syscall 수준까지 Rust로 만들었다. 반대로 libbpfgo는 libbpf C 코드를 감싼 Go 래퍼이고, libbpf-rs는 libbpf의 Rust 래퍼다. 책은 libbpfgo에 대해 CGo 경계가 성능 등의 문제를 낳을 수 있다는 우려도 적었다(p.193-197). 이 분류는 2026년 소스에서도 그대로다. 2절의 빈칸을 누가 채우는지로 다시 정리하면 다음과 같다. 라이브러리 빈칸을 채우는 코드 근거(태그 기준 소스) libbpf(C) libbpf 자신 bpf_object__relocate_data() , bpf_object__relocate_core() (v1.7.0) libbpfgo(Go) 링크된 C libbpf(시스템 공유 라이브러리 또는 정적 vendoring). Go는 CGo로 bpf_object__load() 를 호출 module.go 의 C.bpf_object__load(m.obj) , README의 dynamic/static 빌드 (v0.11.0-libbpf-1.8-dev-2bbc483) libbpf-rs(Rust) C libbpf(기본값은 vendored libbpf). Rust는 FFI로 같은 함수 호출 object.rs 의 libbpf_sys::bpf_object__load(...) (v0.27.2) ebpf-go(Go) Go로 재구현. CO-RE 코드는 "derived from libbpf"라고 명시 btf/core.go , linker.go 의 applyRelocations , fixupKfuncs → sys.ProgLoad (v0.22.0) Aya(Rust) Rust로 재구현. libbpf·bcc 비의존 aya-obj/src/relocation.rs 가 BPF_PSEUDO_MAP_FD 와 fd를 기록 (aya-obj-v0.3.0) Aya의 map 재배치 코드는 libbpf와 같은 빈칸에 같은 종류의 값을 적는다. /* aya-obj/src/relocation.rs, aya-obj-v0.3.0, 263-268행 발췌 */ instructions[ins_index].set_src_reg(BPF_PSEUDO_MAP_FD as u8); ... instructions[ins_index].imm = *fd; 소스 - aya-obj/src/relocation.rs#L263-L268 이 구분은 취향 문제가 아니다. 래퍼(libbpfgo, libbpf-rs)의 재배치 기능과 버그는 실제로 링크된 libbpf 버전을 그대로 따라간다. libbpf-rs는 기본값이 vendored libbpf이고, libbpfgo는 시스템 libbpf에 동적으로 링크하거나 정적으로 넣는 방식을 고를 수 있다. 재구현(ebpf-go, Aya)은 알고리즘의 출처가 libbpf라도 코드가 별개라서, 새 재배치 종류나 커널 기능을 지원하는 시점이 libbpf와 다를 수 있다. 구체적인 시차 사례는 이 글에서 조사하지 않았다. 커널 쪽 코드를 무엇으로 컴파일하는지도 갈린다. C는 Clang이 주류이고, Aya는 rustc의 BPF 타깃으로 커널 쪽 코드까지 Rust로 쓴다. 여기서 Aya의 libbpf 비의존은 로더 구현에 대한 설명이다. 커널 쪽 프로그램의 빌드 도구 체인까지 LLVM과 무관하다는 뜻으로 확대하면 안 된다. 책은 GCC도 버전 10부터 eBPF를 지원하지만 LLVM 대비 공백이 있다고 했다(p.191). 2024년 9월 LWN 보도에 따르면 GCC BPF 백엔드는 커널 테스트 케이스를 모두 컴파일할 수 있지만 아직 전부 올바르게 돌지는 않았고, CO-RE selftest는 GCC 빌드로 통과했다. 2025년 LSF/MM/BPF의 BPF CI 발표도 GCC가 selftest를 .bpf.o 로 빌드하는 데는 성공하지만 "About half of the tests fail, so we don't run them yet"이라고 했다. 남은 공백이 "컴파일할 수 있는가"에서 "만든 코드가 verifier를 통과하고 올바르게 도는가"로 옮겨 간 것이다. 실패 원인별 내역은 발표에 나오지 않는다. 이 수치는 2025년 발표 시점의 관찰이고, 2026년 현재 실패율은 확인하지 않았다. 4. 책의 라이브러리 목록을 2026년에 다시 보면 책은 몇 가지 판정을 남겼다. gobpf는 한동안 유지보수되지 않았고 deprecate 논의가 있다. ebpf.io 분류에서 Aya는 "emerging", redbpf는 major 프로젝트였지만, 흐름은 Aya 쪽으로 옮겨 가는 것 같다(p.193, p.197). 2026-10-02에 GitHub API로 각 저장소를 조회한 결과는 아래와 같다. 판정은 archived 여부, README 문구, 마지막 릴리스와 푸시 날짜로만 내렸다. 스타 수는 판정에 쓰지 않았다. 라이브러리 2026년 판정 근거 신호 redbpf 종료(공식 아카이브) archived=true, 마지막 릴리스 v2.3.0(2022-01), 기본 브랜치 마지막 커밋 2022-12 rust-bcc 종료(공식 비유지보수 선언) archived=false, README "Warning! Unmaintained!", 마지막 커밋 2023-10 gobpf 선언 없는 휴면 archived=false, deprecation 문구 없음, GitHub 릴리스 없음(최신 태그 v0.2.0), 기본 브랜치 마지막 커밋 2022-10 ebpf-go(cilium/ebpf) 활동 중 v0.22.0(2026-06) libbpfgo 활동 중 v0.11.0-libbpf-1.8-dev-2bbc483(2026-07) libbpf-rs 활동 중 v0.27.2(2026-09) Aya 활동 중 aya-ebpf-v0.2.1(2026-06), aya-v0.14.0 BCC(Python/Lua/C++ 바인딩), libbpf-tools 활동 중 v0.37.0(2026-07). 바인딩과 libbpf-tools는 별도 릴리스 없이 BCC 저장소 하나에 있어, 바인딩별 상태는 따로 판정하지 않았다 libxdp(xdp-tools) 활동 중 v1.6.3(2026-03) 보조 신호로 ebpf.io의 현재 인프라 목록도 봤다. Go 라이브러리로 ebpf-go와 libbpfgo, Rust 라이브러리로 Aya와 libbpf-rs가 올라 있고, gobpf, redbpf, rust-bcc는 없다. 미등재가 곧 폐기 선언은 아니어서 판정에는 위 신호와 함께만 썼다. 책의 예측은 맞았다. Rust 쪽은 redbpf가 아카이브되고 Aya가 남았다. 책이 인용한 ebpf.io의 "emerging" 분류도 지금은 의미가 없다. Aya는 ebpf-go 등과 나란히 언어별 라이브러리로 올라 있다. 다만 "죽었다"에도 종류가 셋이다. gobpf를 "deprecated"라고 쓰면 근거보다 강한 서술이 된다. gobpf에 남은 근거는 기본 브랜치에 2022년 10월 이후 커밋이 없다는 사실뿐이다. 예제 코드도 바뀌었다. 아래는 책 코드와 현재 문서·API가 다른 지점이다. 실제로 로드가 막히는 것은 legacy map을 libbpf 1.0 이상으로 올릴 때이고, Aya의 Bpf 는 deprecated 경고를 낸다. 나머지는 권장 표기와 호출 방식이 바뀐 것이다. 상태 책 원문 서술 실제(2026-10 기준) 근거 표기 변경 // +build ignore (p.193, 책도 //go:build 로 전환 중이라고 언급) ebpf-go 문서는 //go:build ignore ebpf-go Getting Started 호출 방식 변경 go run github.com/cilium/ebpf/cmd/bpf2go -cc $BPF_CLANG ... (p.193) go get -tool .../bpf2go 로 설치 후 go tool bpf2go ... ebpf-go Getting Started 지원 제거(libbpf) struct bpf_map_def SEC("maps") kprobe_map (p.194) libbpf 1.0부터 legacy SEC("maps") 제거, BTF 기반 SEC(".maps") 만 지원. ebpf-go v0.22.0은 아직 maps 섹션을 파싱 libbpf wiki "the road to v1.0", ebpf-go elf_reader.go 이름 변경 Aya Bpf::load(...) (p.198) Aya 0.13.0(2024-10)에서 Bpf 를 Ebpf 로 이름 변경. Bpf 는 deprecated 별칭으로 남음 aya CHANGELOG.md , aya/src/bpf.rs 매크로 변경 Aya #[xdp(name="myapp")] (p.198) #[xdp] 는 이름 인자 없이 섹션을 xdp 로 정하고, 프로그램 이름은 함수 식별자에서 얻음 aya-ebpf-macros xdp.rs 세 번째 행은 로더마다 판정이 다르다는 점에서 2~3절과 이어진다. 같은 .o 를 libbpf 1.0 이상은 거부하고 ebpf-go는 받아 준다. 재구현 라이브러리가 libbpf와 별개 코드라는 것이 실제 동작 차이로 드러나는 사례다. 5. BPF_PROG_RUN과 여러 프로그램: 테스트 명령이 로더가 되기까지 책은 프로그램을 테스트하는 수단으로 BPF_PROG_RUN 을 소개하면서, 이 명령이 "주로 네트워킹 관련인 일부 프로그램 타입"에서만 동작한다고 썼다(p.199). v7.2 커널에서 .test_run 콜백이 등록된 타입을 세면, skb 계열·XDP·flow_dissector·sk_lookup 같은 네트워킹 타입에 더해 raw_tracepoint(CONFIG_NET일 때), tracing(fentry/fexit 등), syscall, struct_ops(CONFIG_NET일 때), netfilter가 있다. 책을 쓰던 무렵의 커널(v6.1)과 비교하면 책의 서술은 당시에도 좁았다. tracing, raw_tracepoint, syscall, struct_ops는 v6.1에 이미 .
То, что RADAR обнаружил и классифицировал для этой возможности. Это опубликованный источником текст, а не подтверждение, что предложение ещё действует.
[Learning eBPF] 10~11장. eBPF Programming / The Future Evolution of eBPF. 컴파일된 eBPF 오브젝트 파일( .o )에 미리 서명해 두면, 커널은 로드할 때 그 서명을 확인할 수 있을까. 2022년 말에 쓰인 책의 답은 "쉽지 않다"였다. 이유는 암호학이 아니었다. 사용자 공간 로더가 map 위치와 CO-RE 정보로 프로그램을 고친 뒤에 커널에 올리기 때문이다. 책은 이것이 서명 관점에서는 악의적 수정과 구분하기 어렵다고 썼다. 9주차 끝에서 "언어별 라이브러리는 커널 쪽 보장을 얼마나 드러내는가"를 물었다. 10장이 소개하는 라이브러리들을 열어 보면 공통으로 하는 일이 하나 있다. 바로 이 "고쳐 쓰기"다. 그리고 11장이 미해결로 남긴 서명 문제는…
Открыть источник