Skip to content

Commit 394f112

Browse files
author
lipeng hao
committed
make stack limit bypass example ok
1 parent 7c4eb22 commit 394f112

File tree

3 files changed

+259
-356
lines changed

3 files changed

+259
-356
lines changed

src/stack_limit_bypass/README.md

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
# Per-CPU Array 规避 eBPF 512B 栈限制
2+
3+
本示例演示如何使用 `BPF_MAP_TYPE_PERCPU_ARRAY` 规避 eBPF 的 512 字节栈限制。
4+
5+
## 问题背景
6+
7+
eBPF 程序的栈空间限制为 **512 字节**。当需要使用大于 512B 的结构体时,直接在栈上分配会被 verifier 拒绝。
8+
9+
```c
10+
// 这样会失败!
11+
SEC("tracepoint/...")
12+
int my_prog(void *ctx) {
13+
struct big_event e; // 544 字节
14+
struct extra_buffer ex; // 768 字节
15+
struct local_data ld; // 256 字节
16+
// verifier 拒绝:总计约 1568 字节,超过 512B 限制
17+
}
18+
```
19+
20+
## 解决方案
21+
22+
使用 **Per-CPU Array** 作为临时缓冲区,将大型数据结构存储在 map 中而非栈上:
23+
24+
```c
25+
struct {
26+
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
27+
__uint(max_entries, 1);
28+
__type(key, __u32);
29+
__type(value, struct big_event);
30+
} buffer SEC(".maps");
31+
32+
SEC("tracepoint/...")
33+
int my_prog(void *ctx) {
34+
__u32 key = 0;
35+
struct big_event *e = bpf_map_lookup_elem(&buffer, &key);
36+
if (!e) return 0;
37+
// 现在可以安全使用 e,不占用栈空间
38+
}
39+
```
40+
41+
## 代码结构
42+
43+
本示例的核心设计是**仅变量分配方式不同,后续处理逻辑完全相同**
44+
45+
```
46+
┌────────────────────────────────────────────────────┐
47+
│ 变量分配(仅此处不同) │
48+
│ ┌─────────────────────┬─────────────────────────┐ │
49+
│ │ BAD_EXAMPLE_STACK │ 默认(Per-CPU Array) │ │
50+
│ ├─────────────────────┼─────────────────────────┤ │
51+
│ struct big_event │ bpf_map_lookup_elem │ │
52+
│ │ struct extra_buffer │ bpf_map_lookup_elem │ │
53+
│ │ struct local_data │ bpf_map_lookup_elem │ │
54+
│ └─────────────────────┴─────────────────────────┘ │
55+
├─────────────────────────────────────────────────────┤
56+
│ 相同的处理逻辑 │
57+
│ 1. 填充事件基本信息 │
58+
│ 2. 处理 extra buffer │
59+
│ 3. 处理 local buffer │
60+
│ 4. 填充 event data │
61+
│ 5. 发送到 Ring Buffer │
62+
└─────────────────────────────────────────────────────┘
63+
```
64+
65+
## 栈使用量分析
66+
67+
| 结构体 | 大小 | 说明 |
68+
|-------|------|------|
69+
| `big_event` | ~544 字节 | pid + timestamp + comm[16] + data[512] |
70+
| `extra_buffer` | ~768 字节 | buf1[256] + buf2[256] + values[32] |
71+
| `local_data` | ~256 字节 | buf[256] |
72+
| **总计** | **~1568 字节** | 远超 512 字节限制 |
73+
74+
## 编译和运行
75+
76+
### 正确示例(默认)
77+
78+
```bash
79+
make clean && make
80+
sudo ./stack_limit_bypass
81+
```
82+
83+
预期输出:
84+
```
85+
========================================
86+
Per-CPU Array 演示 - 规避 eBPF 512B 栈限制
87+
========================================
88+
结构体大小:
89+
- big_event: 544 字节
90+
- 总栈使用量: 约 1568 字节 (使用局部变量时)
91+
- eBPF 栈限制: 512 字节
92+
========================================
93+
监控进程执行事件中... (Ctrl+C 退出)
94+
95+
[12345.678] PID: 1234 | comm: bash | data[0-3]: 0x12 0x34 0x56 0x78
96+
```
97+
98+
### 错误示例:触发栈限制
99+
100+
```bash
101+
make clean && make EXTRA_CFLAGS="-DBAD_EXAMPLE_STACK"
102+
```
103+
104+
预期输出(verifier 拒绝):
105+
```
106+
libbpf: prog 'trace_exec': BPF program is too large
107+
libbpf: prog 'trace_exec': -- BEGIN PROG LOAD LOG --
108+
...
109+
combined stack size of 1568 exceeds limit 512
110+
...
111+
加载 BPF 程序失败
112+
提示: 如果使用 BAD_EXAMPLE_STACK 编译,BPF verifier 会拒绝加载
113+
```
114+
115+
## 防止编译器优化
116+
117+
为确保错误示例能可靠触发栈限制,代码使用了以下技术:
118+
119+
### 1. 内存屏障
120+
121+
```c
122+
#define barrier() asm volatile("" ::: "memory")
123+
124+
struct big_event stack_event = {};
125+
barrier(); // 防止编译器优化掉栈变量
126+
```
127+
128+
### 2. 显式访问多个数组位置
129+
130+
```c
131+
extra->buf1[0] = pid & 0xFF;
132+
extra->buf1[100] = (pid >> 8) & 0xFF;
133+
extra->buf1[200] = (pid >> 16) & 0xFF;
134+
```
135+
136+
### 3. 循环展开
137+
138+
```c
139+
#pragma unroll
140+
for (int i = 0; i < 32; i++) {
141+
extra->values[i] = pid + i;
142+
}
143+
```
144+
145+
## Per-CPU Array 的优势
146+
147+
| 优势 | 说明 |
148+
|------|------|
149+
| **规避栈限制** | 数据存储在 map 中,不占用 eBPF 栈空间 |
150+
| **并发安全** | 每个 CPU 有独立的缓冲区,无需加锁 |
151+
| **高性能** | 无 cacheline 争用,无锁竞争 |
152+
| **零拷贝** | 可直接在 map 中操作数据 |
153+
154+
## 适用场景
155+
156+
- 需要收集大量进程/网络信息的监控程序
157+
- 事件结构体包含多个字符串字段(路径、参数等)
158+
- 高并发场景下的数据采集
159+
- 安全审计和 HIDS 系统
160+
161+
## Tracepoint 说明
162+
163+
本示例使用 `tracepoint/sched/sched_process_exec` 作为 hook 点:
164+
165+
| 属性 | 说明 |
166+
|------|------|
167+
| **触发时机** | 新进程调用 `execve()` 执行时 |
168+
| **触发频率** | 低(只在进程执行新程序时触发) |
169+
| **适用场景** | 监控进程启动、命令执行审计 |
170+
171+
## 内核版本说明
172+
173+
| 内核版本 | 行为 |
174+
|---------|------|
175+
| < 5.x | 严格 512 字节限制,小结构体也会被拒绝 |
176+
| 5.x | 支持 BPF-to-BPF 调用,每个函数帧 512B |
177+
| 6.x | Verifier 更智能,本示例使用 ~1568B 确保触发限制 |
178+
179+
## 文件说明
180+
181+
| 文件 | 说明 |
182+
|------|------|
183+
| `stack_limit_bypass.bpf.c` | BPF 内核程序 |
184+
| `stack_limit_bypass.c` | 用户空间程序 |
185+
| `Makefile` | 构建脚本 |
186+
| `README.md` | 本文档 |

0 commit comments

Comments
 (0)