Phân tích và xử lý lỗi Verifier từ chối BPF Program
Khi triển khai eBPF trên Raspberry Pi CM5 (ARM64), lỗi phổ biến nhất là kernel từ chối load program do bộ kiểm tra (Verifier) phát hiện hành vi không an toàn. Verifier là cơ chế bảo vệ kernel, đảm bảo eBPF program không gây treo hệ thống hoặc truy cập bộ nhớ trái phép.
Bước đầu tiên để debug là đọc log lỗi từ kernel thông qua dmesg hoặc file log /var/log/kern.log. Verifier thường in chi tiết dòng lệnh ASM bị lỗi và lý do (reason).
Chạy lệnh sau để xem log lỗi gần nhất liên quan đến BPF:
sudo dmesg | grep -i "bpf" | tail -n 20
Kết quả mong đợi: Bạn sẽ thấy các dòng bắt đầu bằng [timestamp] bpf_verifier: program rejected kèm theo thông báo cụ thể như invalid access to map value hoặc unbounded memory access.
Cách đọc thông báo lỗi và khắc phục
Nếu gặp lỗi unbounded memory access, nguyên nhân thường là do pointer trong C code không được giới hạn hoặc truy cập mảng vượt quá kích thước map. Verifier không thể xác định được kích thước truy cập tại compile time.
Để debug chi tiết hơn, sử dụng công cụ bpftool để xem bytecode và log verifier với mức độ chi tiết cao. Giả sử bạn có file object là my_program.o:
sudo bpftool bpf load file my_program.o verbose
Kết quả mong đợi: Terminal sẽ in ra toàn bộ luồng kiểm tra của verifier, chỉ ra chính xác instruction nào bị lỗi và trạng thái register tại thời điểm đó.
Nếu lỗi liên quan đến việc sử dụng map không đúng, hãy kiểm tra lại định nghĩa map trong C code. Đảm bảo rằng bạn không truy cập bpf_map_lookup_elem với key không được khởi tạo hoặc pointer NULL.
Sử dụng PERF_EVENT_ARRAY để Debug tràn buffer và mất dữ liệu
Trong môi trường sản xuất trên CM5, vấn đề thường gặp là mất dữ liệu (drop events) do buffer đầy. Điều này xảy ra khi tốc độ sinh sự kiện (event generation rate) vượt quá tốc độ đọc (consumer rate).
Để phát hiện chính xác số lượng sự kiện bị mất, ta phải cấu hình map loại BPF_MAP_TYPE_PERF_EVENT_ARRAY. Kernel tự động duy trì một counter cho số lượng dropped events vào file /sys/kernel/debug/tracing/events/bpf/ hoặc thông qua ring buffer.
Cấu hình BPF Map để theo dõi mất dữ liệu
Trong file C của eBPF program (debug_drop.c), bạn cần khai báo map đặc biệt này. Map này không lưu dữ liệu trực tiếp mà dùng để ghi log vào perf buffer.
Nội dung file /home/pi/ebpf/debug_drop.c:
#include "vmlinux.h"
#include
#include
char LICENSE[] SEC("license") = "GPL";
// Khai báo map để ghi log events
struct {
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
__uint(key_size, sizeof(u32));
__uint(value_size, sizeof(u32));
} events SEC("maps");
// Cấu trúc dữ liệu event
struct event_data {
u64 ts;
u32 pid;
u32 comm[16];
};
SEC("kprobe/sys_write")
int BPF_KPROBE(trace_write)
{
u64 pid_tgid = bpf_get_current_pid_tgid();
u32 pid = pid_tgid >> 32;
struct event_data data = {};
data.ts = bpf_ktime_get_ns();
data.pid = pid;
bpf_get_current_comm(&data.comm, sizeof(data.comm));
// Gửi dữ liệu qua perf buffer
// Nếu buffer đầy, kernel sẽ tự động drop event này và tăng counter dropped
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &data, sizeof(data));
return 0;
}
Kết quả mong đợi: File debug_drop.c được biên dịch thành debug_drop.o thành công mà không bị verifier reject. Map events đã sẵn sàng để ghi log.
Đọc và phân tích số lượng Drop Events
Để đọc dữ liệu và kiểm tra xem có mất dữ liệu hay không, ta dùng công cụ bpftest hoặc viết tool user-space đơn giản. Tuy nhiên, cách nhanh nhất để kiểm tra trạng thái drop là xem file /sys/kernel/debug/tracing/events/bpf/ hoặc sử dụng bpftrace.
Sử dụng bpftrace để theo dõi số lượng dropped events trong real-time:
sudo bpftrace -e 'tracepoint:raw_syscalls:sys_enter { printf("Enter: %d\n", pid); }' 2>&1 | grep -i "drop"
Kết quả mong đợi: Nếu không có dòng "drop" xuất hiện, hệ thống đang hoạt động ổn. Nếu xuất hiện dòng perf_event: dropped events, nghĩa là buffer đã tràn.
Một cách chính xác hơn là dùng bpftool để xem thông tin map sau khi chạy program một thời gian:
sudo bpftool map list | grep -A 5 "PERF_EVENT_ARRAY"
Kết quả mong đợi: Bạn sẽ thấy thống kê về entries và value. Nếu sử dụng bpftrace hoặc perf để đọc, bạn có thể thấy số lượng events bị mất trong output của tool đó.
Các lưu ý về bảo mật và ổn định khi deploy eBPF trong Production
Khi deploy eBPF lên Raspberry Pi CM5 dùng trong môi trường sản xuất (IoT, Edge Gateway), ưu tiên hàng đầu là tính ổn định (stability) và bảo mật (security). Một lỗi trong eBPF có thể làm treo toàn bộ kernel trên thiết bị nhúng.
Hạn chế quyền và Scope của BPF Program
Chỉ load các program thực sự cần thiết. Tránh load program với mode unprivileged nếu không cần thiết. Trên CM5, hãy đảm bảo kernel config đã bật CONFIG_BPF_SYSCALL và tắt CONFIG_BPF_UNPRIVILEGED để chỉ root mới được load program.
Kiểm tra cấu hình kernel hiện tại:
grep CONFIG_BPF_UNPRIVILEGED /boot/config-$(uname -r)
Kết quả mong đợi: Kết quả phải là # CONFIG_BPF_UNPRIVILEGED is not set. Nếu nó là y, bạn cần rebuild kernel hoặc disable option này để tăng bảo mật.
Thiết lập giới hạn tài nguyên (Resource Limits)
Để tránh eBPF program chiếm dụng toàn bộ CPU hoặc RAM của CM5, hãy thiết lập giới hạn số lượng program và map có thể load cùng lúc.
Kiểm tra giới hạn hiện tại của BPF:
cat /proc/sys/kernel/bpf_jit_enable
cat /proc/sys/kernel/bpf_jit_harden
Kết quả mong đợi: bpf_jit_enable phải là 1 (JIT compiler đang bật, giúp hiệu năng trên ARM64). bpf_jit_harden nên để 2 hoặc 3 để tăng cường bảo mật (chặn các kỹ thuật exploit).
Nếu cần giới hạn số lượng program, chỉnh file /proc/sys/kernel/bpf_prog_count:
echo 10 | sudo tee /proc/sys/kernel/bpf_prog_count
Kết quả mong đợi: Hệ thống sẽ từ chối load program mới nếu số lượng đã đạt 10. Điều này giúp tránh tình trạng "memory leak" do quá nhiều program không được unpin.
Quy trình Rollback an toàn
Luôn chuẩn bị sẵn script để unpin và destroy program nếu phát hiện lỗi. Không bao giờ để program chạy "wild" (không quản lý) trong production.
Tạo file script /usr/local/bin/ebpf-kill.sh để kill tất cả program liên quan đến một tên nhất định:
#!/bin/bash
# Script để kill eBPF program theo tên
NAME=$1
if [ -z "$NAME" ]; then
echo "Usage: $0 "
exit 1
fi
# Tìm ID của program
ID=$(sudo bpftool prog list | grep "$NAME" | awk '{print $1}' | cut -d'(' -f1)
if [ -n "$ID" ]; then
echo "Found program ID: $ID. Detaching and deleting..."
sudo bpftool prog detach id $ID
sudo bpftool prog delete id $ID
echo "Program $NAME removed successfully."
else
echo "No program found with name $NAME."
fi
Cấp quyền thực thi:
sudo chmod +x /usr/local/bin/ebpf-kill.sh
Kết quả mong đợi: Khi chạy sudo /usr/local/bin/ebpf-kill.sh my_debug_program, program sẽ bị gỡ bỏ ngay lập tức, giải phóng tài nguyên và ngăn lỗi lan rộng.
Verify kết quả cuối cùng
Để đảm bảo mọi thứ đã được cấu hình đúng, chạy lệnh sau để tổng hợp trạng thái hệ thống eBPF:
sudo bpftool prog list; echo "---"; sudo bpftool map list; echo "---"; cat /proc/sys/kernel/bpf_jit_harden
Kết quả mong đợi: Danh sách program và map hiện có, cùng với mức độ hardening của JIT compiler. Nếu không có program nào chạy mà bạn không mong muốn, và giá trị hardening là >= 2, hệ thống đã sẵn sàng cho production.
Điều hướng series:
Mục lục: Series: Tối ưu hóa Linux Kernel cho Raspberry Pi CM5 với BPF và eBPF
« Phần 8: Xây dựng dashboard giám sát thời gian thực với eBPF