BPF 程序上传解密 "invalid mem access map_ptr"
deciphering "invalid mem access map_ptr" on BPF program upload
我正在编写一个读取 C 代码的自定义应用程序,调用 LLVM 以从该 C 代码生成 BPF 字节代码,然后重新定位任何 bpf 映射符号并将其上传到内核。我可以成功上传 运行 不使用 BPF 映射的程序,但是一旦我重新定位程序以使用 BPF 映射,我就会收到以下错误:
invalid mem access 'map_ptr'
详情如下:
将以下输入提供给 LLVM:
// Placeholder values for user-requested maps
void *a;
#include <linux/ptrace.h>
#include <uapi/linux/bpf.h>
#include "bpf_helpers.h"
int kprobe__blk_start_request(struct pt_regs *ctx) {
long rq = PT_REGS_PARM1(ctx);
u64 val = bpf_ktime_get_ns();
bpf_map_update_elem(a, &rq, &val, BPF_ANY);
return 0;
}
生成以下字节码
Found function: kprobe__blk_start_request
size: 120 bytes
addr: 0
relocate: a @ 32
/tmp/bpf7990/bpf.o: file format ELF64-BPF
Disassembly of section .text:
kprobe__blk_start_request:
0: 79 11 70 00 00 00 00 00 r1 = *(u64 *)(r1 + 112)
1: 7b 1a f8 ff 00 00 00 00 *(u64 *)(r10 - 8) = r1
2: 85 00 00 00 05 00 00 00 call 5
3: 7b 0a f0 ff 00 00 00 00 *(u64 *)(r10 - 16) = r0
4: 18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0ll
6: 79 11 00 00 00 00 00 00 r1 = *(u64 *)(r1 + 0)
7: bf a2 00 00 00 00 00 00 r2 = r10
8: 07 02 00 00 f8 ff ff ff r2 += -8
9: bf a3 00 00 00 00 00 00 r3 = r10
10: 07 03 00 00 f0 ff ff ff r3 += -16
11: b7 04 00 00 00 00 00 00 r4 = 0
12: 85 00 00 00 02 00 00 00 call 2
13: b7 00 00 00 00 00 00 00 r0 = 0
14: 95 00 00 00 00 00 00 00 exit
和 "a" 符号在指令 #4 中重新定位如下:
Relocating instruction 4
from code:0x18 dst_reg:1 src_reg:0 off:0x0 imm:0x0
to code:0x18 dst_reg:1 src_reg:1 off:0x0 imm:0x4
kprobe__blk_start_request:
0: 79 11 70 00 00 00 00 00
1: 7b 1a f8 ff 00 00 00 00
2: 85 00 00 00 05 00 00 00
3: 7b 0a f0 ff 00 00 00 00
4: 18 11 00 00 04 00 00 00
5: 00 00 00 00 00 00 00 00
6: 79 11 00 00 00 00 00 00
7: bf a2 00 00 00 00 00 00
8: 07 02 00 00 f8 ff ff ff
9: bf a3 00 00 00 00 00 00
10: 07 03 00 00 f0 ff ff ff
11: b7 04 00 00 00 00 00 00
12: 85 00 00 00 02 00 00 00
13: b7 00 00 00 00 00 00 00
14: 95 00 00 00 00 00 00 00
指令 #4 中的“04”是符号 "a" 映射的 FD。
调用 BPF_PROG_LOAD 后,我在内核的错误日志中得到以下内容。
Failed to load kprobe__blk_start_request BPF code
0: R1=ctx(id=0,off=0,imm=0) R10=fp0
0: (79) r1 = *(u64 *)(r1 +112)
1: R1=inv(id=0) R10=fp0
1: (7b) *(u64 *)(r10 -8) = r1
2: R1=inv(id=0) R10=fp0
2: (85) call bpf_ktime_get_ns#5
3: R0=inv(id=0) R10=fp0
3: (7b) *(u64 *)(r10 -16) = r0
4: R0=inv(id=0) R10=fp0
4: (18) r1 = 0xffff88042be0b000
6: R0=inv(id=0) R1=map_ptr(id=0,off=0,ks=8,vs=8) R10=fp0
6: (79) r1 = *(u64 *)(r1 +0)
R1 invalid mem access 'map_ptr'
Errno: 13 (Permission denied)
我无法解读此错误日志。内核试图告诉我什么?
错误解释
invalid mem access 'map_ptr'
表示您正在尝试从无效的内存位置读取,尤其是映射指针指向的位置。
确实,参考你的字节码:
4: 18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0ll
6: 79 11 00 00 00 00 00 00 r1 = *(u64 *)(r1 + 0)
你首先在r1中加载一个立即数,然后读取r1指向的内存位置。但是,内核验证程序将 BPF_LD_IMM 指令识别为映射指针加载。所以,it tags r1 with the map_ptr
type. The verifier then rejects instruction #6 because it tries to read the memory location pointed to by the map pointer(你只应该将它传递给映射助手,而不是在 BPF 程序中使用它)。
基本上,指令 #6 既无效又不需要。没有它,你的程序应该通过验证器。
可能修复
我没有你的重定位代码,所以我无法重现,但我猜这个无效的字节码与你声明地图的方式有关。如果您查看 BPF samples in the Linux kernel,您会发现 映射通常被声明为第一个映射辅助参数指向的全局结构 :
// Placeholder values for user-requested maps
struct bpf_map_def a = {
.type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(u32),
.value_size = sizeof(int),
.max_entries = 1024,
};
#include <linux/ptrace.h>
#include <uapi/linux/bpf.h>
#include "bpf_helpers.h"
int kprobe__blk_start_request(struct pt_regs *ctx) {
long rq = PT_REGS_PARM1(ctx);
u64 val = bpf_ktime_get_ns();
bpf_map_update_elem(&a, &rq, &val, BPF_ANY);
return 0;
}
我正在编写一个读取 C 代码的自定义应用程序,调用 LLVM 以从该 C 代码生成 BPF 字节代码,然后重新定位任何 bpf 映射符号并将其上传到内核。我可以成功上传 运行 不使用 BPF 映射的程序,但是一旦我重新定位程序以使用 BPF 映射,我就会收到以下错误:
invalid mem access 'map_ptr'
详情如下:
将以下输入提供给 LLVM:
// Placeholder values for user-requested maps
void *a;
#include <linux/ptrace.h>
#include <uapi/linux/bpf.h>
#include "bpf_helpers.h"
int kprobe__blk_start_request(struct pt_regs *ctx) {
long rq = PT_REGS_PARM1(ctx);
u64 val = bpf_ktime_get_ns();
bpf_map_update_elem(a, &rq, &val, BPF_ANY);
return 0;
}
生成以下字节码
Found function: kprobe__blk_start_request
size: 120 bytes
addr: 0
relocate: a @ 32
/tmp/bpf7990/bpf.o: file format ELF64-BPF
Disassembly of section .text:
kprobe__blk_start_request:
0: 79 11 70 00 00 00 00 00 r1 = *(u64 *)(r1 + 112)
1: 7b 1a f8 ff 00 00 00 00 *(u64 *)(r10 - 8) = r1
2: 85 00 00 00 05 00 00 00 call 5
3: 7b 0a f0 ff 00 00 00 00 *(u64 *)(r10 - 16) = r0
4: 18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0ll
6: 79 11 00 00 00 00 00 00 r1 = *(u64 *)(r1 + 0)
7: bf a2 00 00 00 00 00 00 r2 = r10
8: 07 02 00 00 f8 ff ff ff r2 += -8
9: bf a3 00 00 00 00 00 00 r3 = r10
10: 07 03 00 00 f0 ff ff ff r3 += -16
11: b7 04 00 00 00 00 00 00 r4 = 0
12: 85 00 00 00 02 00 00 00 call 2
13: b7 00 00 00 00 00 00 00 r0 = 0
14: 95 00 00 00 00 00 00 00 exit
和 "a" 符号在指令 #4 中重新定位如下:
Relocating instruction 4
from code:0x18 dst_reg:1 src_reg:0 off:0x0 imm:0x0
to code:0x18 dst_reg:1 src_reg:1 off:0x0 imm:0x4
kprobe__blk_start_request:
0: 79 11 70 00 00 00 00 00
1: 7b 1a f8 ff 00 00 00 00
2: 85 00 00 00 05 00 00 00
3: 7b 0a f0 ff 00 00 00 00
4: 18 11 00 00 04 00 00 00
5: 00 00 00 00 00 00 00 00
6: 79 11 00 00 00 00 00 00
7: bf a2 00 00 00 00 00 00
8: 07 02 00 00 f8 ff ff ff
9: bf a3 00 00 00 00 00 00
10: 07 03 00 00 f0 ff ff ff
11: b7 04 00 00 00 00 00 00
12: 85 00 00 00 02 00 00 00
13: b7 00 00 00 00 00 00 00
14: 95 00 00 00 00 00 00 00
指令 #4 中的“04”是符号 "a" 映射的 FD。 调用 BPF_PROG_LOAD 后,我在内核的错误日志中得到以下内容。
Failed to load kprobe__blk_start_request BPF code
0: R1=ctx(id=0,off=0,imm=0) R10=fp0
0: (79) r1 = *(u64 *)(r1 +112)
1: R1=inv(id=0) R10=fp0
1: (7b) *(u64 *)(r10 -8) = r1
2: R1=inv(id=0) R10=fp0
2: (85) call bpf_ktime_get_ns#5
3: R0=inv(id=0) R10=fp0
3: (7b) *(u64 *)(r10 -16) = r0
4: R0=inv(id=0) R10=fp0
4: (18) r1 = 0xffff88042be0b000
6: R0=inv(id=0) R1=map_ptr(id=0,off=0,ks=8,vs=8) R10=fp0
6: (79) r1 = *(u64 *)(r1 +0)
R1 invalid mem access 'map_ptr'
Errno: 13 (Permission denied)
我无法解读此错误日志。内核试图告诉我什么?
错误解释
invalid mem access 'map_ptr'
表示您正在尝试从无效的内存位置读取,尤其是映射指针指向的位置。
确实,参考你的字节码:
4: 18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0ll
6: 79 11 00 00 00 00 00 00 r1 = *(u64 *)(r1 + 0)
你首先在r1中加载一个立即数,然后读取r1指向的内存位置。但是,内核验证程序将 BPF_LD_IMM 指令识别为映射指针加载。所以,it tags r1 with the map_ptr
type. The verifier then rejects instruction #6 because it tries to read the memory location pointed to by the map pointer(你只应该将它传递给映射助手,而不是在 BPF 程序中使用它)。
基本上,指令 #6 既无效又不需要。没有它,你的程序应该通过验证器。
可能修复
我没有你的重定位代码,所以我无法重现,但我猜这个无效的字节码与你声明地图的方式有关。如果您查看 BPF samples in the Linux kernel,您会发现 映射通常被声明为第一个映射辅助参数指向的全局结构 :
// Placeholder values for user-requested maps
struct bpf_map_def a = {
.type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(u32),
.value_size = sizeof(int),
.max_entries = 1024,
};
#include <linux/ptrace.h>
#include <uapi/linux/bpf.h>
#include "bpf_helpers.h"
int kprobe__blk_start_request(struct pt_regs *ctx) {
long rq = PT_REGS_PARM1(ctx);
u64 val = bpf_ktime_get_ns();
bpf_map_update_elem(&a, &rq, &val, BPF_ANY);
return 0;
}