Skip to content

Commit 1d6e42e

Browse files
committed
bpf: Report arena faults to BPF stderr
Begin reporting arena page faults and the faulting address to BPF program's stderr, this patch adds support in the arm64 and x86-64 JITs, support for other archs can be added later. The fault handlers receive the 32 bit address in the arena region so the upper 32 bits of user_vm_start is added to it before printing the address. This is what the user would expect to see as this is what is printed by bpf_printk() is you pass it an address returned by bpf_arena_alloc_pages(); Signed-off-by: Puranjay Mohan <[email protected]> Acked-by: Yonghong Song <[email protected]>
1 parent 7e5a702 commit 1d6e42e

File tree

4 files changed

+127
-4
lines changed

4 files changed

+127
-4
lines changed

arch/arm64/net/bpf_jit_comp.c

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,18 +1066,32 @@ static void build_epilogue(struct jit_ctx *ctx, bool was_classic)
10661066
emit(A64_RET(A64_LR), ctx);
10671067
}
10681068

1069+
#define BPF_FIXUP_OFFSET_MASK GENMASK(15, 0)
1070+
#define BPF_FIXUP_ARENA_REG_MASK GENMASK(20, 16)
1071+
#define BPF_ARENA_ACCESS BIT(21)
10691072
#define BPF_FIXUP_REG_MASK GENMASK(31, 27)
10701073
#define DONT_CLEAR 5 /* Unused ARM64 register from BPF's POV */
10711074

10721075
bool ex_handler_bpf(const struct exception_table_entry *ex,
10731076
struct pt_regs *regs)
10741077
{
10751078
int dst_reg = FIELD_GET(BPF_FIXUP_REG_MASK, ex->fixup);
1079+
s16 off = FIELD_GET(BPF_FIXUP_OFFSET_MASK, ex->fixup);
1080+
int arena_reg = FIELD_GET(BPF_FIXUP_ARENA_REG_MASK, ex->fixup);
1081+
bool is_arena = !!(ex->fixup & BPF_ARENA_ACCESS);
1082+
bool is_write = (dst_reg == DONT_CLEAR);
1083+
unsigned long addr;
10761084

10771085
if (dst_reg != DONT_CLEAR)
10781086
regs->regs[dst_reg] = 0;
10791087
/* Skip the faulting instruction */
10801088
regs->pc += AARCH64_INSN_SIZE;
1089+
1090+
if (is_arena) {
1091+
addr = regs->regs[arena_reg] + off;
1092+
bpf_prog_report_arena_violation(is_write, addr);
1093+
}
1094+
10811095
return true;
10821096
}
10831097

@@ -1087,6 +1101,9 @@ static int add_exception_handler(const struct bpf_insn *insn,
10871101
int dst_reg)
10881102
{
10891103
off_t ins_offset;
1104+
s16 off = insn->off;
1105+
bool is_arena;
1106+
int arena_reg;
10901107
unsigned long pc;
10911108
struct exception_table_entry *ex;
10921109

@@ -1100,6 +1117,9 @@ static int add_exception_handler(const struct bpf_insn *insn,
11001117
BPF_MODE(insn->code) != BPF_PROBE_ATOMIC)
11011118
return 0;
11021119

1120+
is_arena = (BPF_MODE(insn->code) == BPF_PROBE_MEM32) ||
1121+
(BPF_MODE(insn->code) == BPF_PROBE_ATOMIC);
1122+
11031123
if (!ctx->prog->aux->extable ||
11041124
WARN_ON_ONCE(ctx->exentry_idx >= ctx->prog->aux->num_exentries))
11051125
return -EINVAL;
@@ -1131,6 +1151,17 @@ static int add_exception_handler(const struct bpf_insn *insn,
11311151

11321152
ex->fixup = FIELD_PREP(BPF_FIXUP_REG_MASK, dst_reg);
11331153

1154+
if (is_arena) {
1155+
ex->fixup |= BPF_ARENA_ACCESS;
1156+
if (BPF_CLASS(insn->code) == BPF_LDX)
1157+
arena_reg = bpf2a64[insn->src_reg];
1158+
else
1159+
arena_reg = bpf2a64[insn->dst_reg];
1160+
1161+
ex->fixup |= FIELD_PREP(BPF_FIXUP_OFFSET_MASK, off) |
1162+
FIELD_PREP(BPF_FIXUP_ARENA_REG_MASK, arena_reg);
1163+
}
1164+
11341165
ex->type = EX_TYPE_BPF;
11351166

11361167
ctx->exentry_idx++;

arch/x86/net/bpf_jit_comp.c

Lines changed: 75 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <linux/netdevice.h>
99
#include <linux/filter.h>
1010
#include <linux/if_vlan.h>
11+
#include <linux/bitfield.h>
1112
#include <linux/bpf.h>
1213
#include <linux/memory.h>
1314
#include <linux/sort.h>
@@ -1388,16 +1389,67 @@ static int emit_atomic_ld_st_index(u8 **pprog, u32 atomic_op, u32 size,
13881389
return 0;
13891390
}
13901391

1392+
/*
1393+
* Metadata encoding for exception handling in JITed code.
1394+
*
1395+
* Format of `fixup` and `data` fields in `struct exception_table_entry`:
1396+
*
1397+
* Bit layout of `fixup` (32-bit):
1398+
*
1399+
* +-----------+--------+-----------+---------+----------+
1400+
* | 31 | 30-24 | 23-16 | 15-8 | 7-0 |
1401+
* | | | | | |
1402+
* | ARENA_ACC | Unused | ARENA_REG | DST_REG | INSN_LEN |
1403+
* +-----------+--------+-----------+---------+----------+
1404+
*
1405+
* - INSN_LEN (8 bits): Length of faulting insn (max x86 insn = 15 bytes (fits in 8 bits)).
1406+
* - DST_REG (8 bits): Offset of dst_reg from reg2pt_regs[] (max offset = 112 (fits in 8 bits)).
1407+
* This is set to DONT_CLEAR if the insn is a store.
1408+
* - ARENA_REG (8 bits): Offset of the register that is used to calculate the
1409+
* address for load/store when accessing the arena region.
1410+
* - ARENA_ACCESS (1 bit): This bit is set when the faulting instruction accessed the arena region.
1411+
*
1412+
* Bit layout of `data` (32-bit):
1413+
*
1414+
* +--------------+--------+--------------+
1415+
* | 31-16 | 15-8 | 7-0 |
1416+
* | | | |
1417+
* | ARENA_OFFSET | Unused | EX_TYPE_BPF |
1418+
* +--------------+--------+--------------+
1419+
*
1420+
* - ARENA_OFFSET (16 bits): Offset used to calculate the address for load/store when
1421+
* accessing the arena region.
1422+
*/
1423+
13911424
#define DONT_CLEAR 1
1425+
#define FIXUP_INSN_LEN_MASK GENMASK(7, 0)
1426+
#define FIXUP_REG_MASK GENMASK(15, 8)
1427+
#define FIXUP_ARENA_REG_MASK GENMASK(23, 16)
1428+
#define FIXUP_ARENA_ACCESS BIT(31)
1429+
#define DATA_ARENA_OFFSET_MASK GENMASK(31, 16)
13921430

13931431
bool ex_handler_bpf(const struct exception_table_entry *x, struct pt_regs *regs)
13941432
{
1395-
u32 reg = x->fixup >> 8;
1433+
u32 reg = FIELD_GET(FIXUP_REG_MASK, x->fixup);
1434+
u32 insn_len = FIELD_GET(FIXUP_INSN_LEN_MASK, x->fixup);
1435+
bool is_arena = !!(x->fixup & FIXUP_ARENA_ACCESS);
1436+
bool is_write = (reg == DONT_CLEAR);
1437+
unsigned long addr;
1438+
s16 off;
1439+
u32 arena_reg;
13961440

13971441
/* jump over faulting load and clear dest register */
13981442
if (reg != DONT_CLEAR)
13991443
*(unsigned long *)((void *)regs + reg) = 0;
1400-
regs->ip += x->fixup & 0xff;
1444+
regs->ip += insn_len;
1445+
1446+
if (is_arena) {
1447+
arena_reg = FIELD_GET(FIXUP_ARENA_REG_MASK, x->data);
1448+
off = FIELD_GET(DATA_ARENA_OFFSET_MASK, x->data);
1449+
addr = *(unsigned long *)((void *)regs + arena_reg) + off;
1450+
bpf_prog_report_arena_violation(is_write, addr);
1451+
}
1452+
14011453
return true;
14021454
}
14031455

@@ -2070,6 +2122,8 @@ st: if (is_imm8(insn->off))
20702122
{
20712123
struct exception_table_entry *ex;
20722124
u8 *_insn = image + proglen + (start_of_ldx - temp);
2125+
u32 arena_reg, fixup_reg;
2126+
bool is_arena;
20732127
s64 delta;
20742128

20752129
if (!bpf_prog->aux->extable)
@@ -2089,8 +2143,25 @@ st: if (is_imm8(insn->off))
20892143

20902144
ex->data = EX_TYPE_BPF;
20912145

2092-
ex->fixup = (prog - start_of_ldx) |
2093-
((BPF_CLASS(insn->code) == BPF_LDX ? reg2pt_regs[dst_reg] : DONT_CLEAR) << 8);
2146+
is_arena = (BPF_MODE(insn->code) == BPF_PROBE_MEM32) ||
2147+
(BPF_MODE(insn->code) == BPF_PROBE_ATOMIC);
2148+
2149+
fixup_reg = (BPF_CLASS(insn->code) == BPF_LDX) ?
2150+
reg2pt_regs[dst_reg] : DONT_CLEAR;
2151+
2152+
ex->fixup = FIELD_PREP(FIXUP_OFFSET_MASK, prog - start_of_ldx) |
2153+
FIELD_PREP(FIXUP_REG_MASK, fixup_reg);
2154+
2155+
if (is_arena) {
2156+
ex->fixup |= FIXUP_ARENA_ACCESS;
2157+
if (BPF_CLASS(insn->code) == BPF_LDX)
2158+
arena_reg = reg2pt_regs[src_reg];
2159+
else
2160+
arena_reg = reg2pt_regs[dst_reg];
2161+
2162+
ex->fixup |= FIELD_PREP(FIXUP_ARENA_REG_MASK, arena_reg);
2163+
ex->data |= FIELD_PREP(DATA_ARENA_OFFSET_MASK, insn->off);
2164+
}
20942165
}
20952166
break;
20962167

include/linux/bpf.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3654,6 +3654,7 @@ int bpf_stream_stage_printk(struct bpf_stream_stage *ss, const char *fmt, ...);
36543654
int bpf_stream_stage_commit(struct bpf_stream_stage *ss, struct bpf_prog *prog,
36553655
enum bpf_stream_id stream_id);
36563656
int bpf_stream_stage_dump_stack(struct bpf_stream_stage *ss);
3657+
void bpf_prog_report_arena_violation(bool write, unsigned long addr);
36573658

36583659
#define bpf_stream_printk(ss, ...) bpf_stream_stage_printk(&ss, __VA_ARGS__)
36593660
#define bpf_stream_dump_stack(ss) bpf_stream_stage_dump_stack(&ss)

kernel/bpf/arena.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,3 +633,23 @@ static int __init kfunc_init(void)
633633
return register_btf_kfunc_id_set(BPF_PROG_TYPE_UNSPEC, &common_kfunc_set);
634634
}
635635
late_initcall(kfunc_init);
636+
637+
void bpf_prog_report_arena_violation(bool write, unsigned long addr)
638+
{
639+
struct bpf_stream_stage ss;
640+
struct bpf_prog *prog;
641+
u64 user_vm_start;
642+
643+
prog = bpf_prog_find_from_stack();
644+
if (!prog)
645+
return;
646+
647+
user_vm_start = bpf_arena_get_user_vm_start(prog->aux->arena);
648+
addr += (user_vm_start >> 32) << 32;
649+
650+
bpf_stream_stage(ss, prog, BPF_STDERR, ({
651+
bpf_stream_printk(ss, "ERROR: Arena %s access at unmapped address 0x%lx\n",
652+
write ? "WRITE" : "READ", addr);
653+
bpf_stream_dump_stack(ss);
654+
}));
655+
}

0 commit comments

Comments
 (0)