Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions arch/x86/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ config X86
select HAVE_SAMPLE_FTRACE_DIRECT if X86_64
select HAVE_SAMPLE_FTRACE_DIRECT_MULTI if X86_64
select HAVE_EBPF_JIT
select HAVE_EBPF_JIT_KASAN if X86_64
select HAVE_EFFICIENT_UNALIGNED_ACCESS
select HAVE_EISA if X86_32
select HAVE_EXIT_THREAD
Expand Down
271 changes: 217 additions & 54 deletions arch/x86/net/bpf_jit_comp.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@
#include <asm/unwind.h>
#include <asm/cfi.h>

#if IS_ENABLED(CONFIG_BPF_JIT_KASAN)
void __asan_load1(void *p);
void __asan_store1(void *p);
void __asan_load2(void *p);
void __asan_store2(void *p);
void __asan_load4(void *p);
void __asan_store4(void *p);
void __asan_load8(void *p);
void __asan_store8(void *p);
#endif

static bool all_callee_regs_used[4] = {true, true, true, true};

static u8 *emit_code(u8 *ptr, u32 bytes, unsigned int len)
Expand Down Expand Up @@ -1110,6 +1121,90 @@ static void maybe_emit_1mod(u8 **pprog, u32 reg, bool is64)
*pprog = prog;
}

static int emit_kasan_check(struct bpf_verifier_env *env, u8 **pprog,
u32 addr_reg, struct bpf_insn *insn, u8 *ip,
bool is_write, bool accesses_stack_only)
{
#ifdef CONFIG_BPF_JIT_KASAN
u32 bpf_size = BPF_SIZE(insn->code);
s32 off = insn->off;
u8 *prog = *pprog;
void *kasan_func;

if (!env)
return 0;

if (accesses_stack_only)
return 0;

/* Derive KASAN check function from access type and size */
switch (bpf_size) {
case BPF_B:
kasan_func = is_write ? __asan_store1 : __asan_load1;
break;
case BPF_H:
kasan_func = is_write ? __asan_store2 : __asan_load2;
break;
case BPF_W:
kasan_func = is_write ? __asan_store4 : __asan_load4;
break;
case BPF_DW:
kasan_func = is_write ? __asan_store8 : __asan_load8;
break;
default:
return -EINVAL;
}

/* Save rax */
EMIT1(0x50);
/* Save rcx */
EMIT1(0x51);
/* Save rdx */
EMIT1(0x52);
/* Save rsi */
EMIT1(0x56);
/* Save rdi */
EMIT1(0x57);
/* Save r8 */
EMIT2(0x41, 0x50);
/* Save r9 */
EMIT2(0x41, 0x51);

/* mov rdi, addr_reg */
EMIT_mov(BPF_REG_1, addr_reg);

/* add rdi, off (if offset is non-zero) */
if (off) {
if (is_imm8(off)) {
/* add rdi, imm8 */
EMIT4(0x48, 0x83, 0xC7, (u8)off);
} else {
/* add rdi, imm32 */
EMIT3_off32(0x48, 0x81, 0xC7, off);
}
}

/* Adjust ip to account for the instrumentation generated so far */
ip += (prog - *pprog);
/* We emit a call, so update call depth counting */
ip += x86_call_depth_emit_accounting(&prog, kasan_func, ip);
/* call kasan_func */
if (emit_call(&prog, kasan_func, ip))
return -ERANGE;

EMIT2(0x41, 0x59);
EMIT2(0x41, 0x58);
EMIT1(0x5F);
EMIT1(0x5E);
EMIT1(0x5A);
EMIT1(0x59);
EMIT1(0x58);

*pprog = prog;
#endif /* CONFIG_BPF_JIT_KASAN */
return 0;
}

/* LDX: dst_reg = *(u8*)(src_reg + off) */
static void emit_ldx(u8 **pprog, u32 size, u32 dst_reg, u32 src_reg, int off)
{
Expand Down Expand Up @@ -1315,6 +1410,64 @@ static void emit_st_index(u8 **pprog, u32 size, u32 dst_reg, u32 index_reg, int
*pprog = prog;
}

static void emit_st(u8 **pprog, struct bpf_insn *insn, int dst_reg,
s32 outgoing_arg_base, u16 outgoing_rsp)
{
s32 imm32 = insn->imm;
u8 *prog = *pprog;
s32 insn_off;

switch (BPF_SIZE(insn->code)) {
case BPF_B:
if (is_ereg(dst_reg))
EMIT2(0x41, 0xC6);
else
EMIT1(0xC6);
break;
case BPF_H:
if (is_ereg(dst_reg))
EMIT3(0x66, 0x41, 0xC7);
else
EMIT2(0x66, 0xC7);
break;
case BPF_W:
if (is_ereg(dst_reg))
EMIT2(0x41, 0xC7);
else
EMIT1(0xC7);
break;
case BPF_DW:
if (dst_reg == BPF_REG_PARAMS && insn->off == -8) {
/* Arg 6: store immediate in r9 register */
emit_mov_imm64(&prog, X86_REG_R9, imm32 >> 31,
(u32)imm32);
*pprog = prog;
return;
}
EMIT2(add_1mod(0x48, dst_reg), 0xC7);
break;
}

insn_off = insn->off;
if (dst_reg == BPF_REG_PARAMS) {
/*
* Args 7+: reverse BPF negative offsets to
* x86 positive rsp offsets.
* BPF off=-16 → [rsp+0], off=-24 → [rsp+8], ...
*/
insn_off = outgoing_arg_base - outgoing_rsp -
insn_off - 16;
dst_reg = BPF_REG_FP;
}
if (is_imm8(insn_off))
EMIT2(add_1reg(0x40, dst_reg), insn_off);
else
EMIT1_off32(add_1reg(0x80, dst_reg), insn_off);

EMIT(imm32, bpf_size_to_x86_bytes(BPF_SIZE(insn->code)));
*pprog = prog;
}

static void emit_st_r12(u8 **pprog, u32 size, u32 dst_reg, int off, int imm)
{
emit_st_index(pprog, size, dst_reg, X86_REG_R12, off, imm);
Expand Down Expand Up @@ -1423,17 +1576,31 @@ static int emit_atomic_rmw_index(u8 **pprog, u32 atomic_op, u32 size,
return 0;
}

static int emit_atomic_ld_st(u8 **pprog, u32 atomic_op, u32 dst_reg,
u32 src_reg, s16 off, u8 bpf_size)
static int emit_atomic_ld_st(struct bpf_verifier_env *env, u8 **pprog,
struct bpf_insn *insn, u8 *ip, u32 dst_reg,
u32 src_reg, bool accesses_stack_only)
{
u32 atomic_op = insn->imm;
int err;

switch (atomic_op) {
case BPF_LOAD_ACQ:
err = emit_kasan_check(env, pprog, src_reg, insn, ip, false,
accesses_stack_only);
if (err)
return err;
/* dst_reg = smp_load_acquire(src_reg + off16) */
emit_ldx(pprog, bpf_size, dst_reg, src_reg, off);
emit_ldx(pprog, BPF_SIZE(insn->code), dst_reg, src_reg,
insn->off);
break;
case BPF_STORE_REL:
err = emit_kasan_check(env, pprog, dst_reg, insn, ip, true,
accesses_stack_only);
if (err)
return err;
/* smp_store_release(dst_reg + off16, src_reg) */
emit_stx(pprog, bpf_size, dst_reg, src_reg, off);
emit_stx(pprog, BPF_SIZE(insn->code), dst_reg, src_reg,
insn->off);
break;
default:
pr_err("bpf_jit: unknown atomic load/store opcode %02x\n",
Expand Down Expand Up @@ -1811,10 +1978,12 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
const s32 imm32 = insn->imm;
u32 dst_reg = insn->dst_reg;
u32 src_reg = insn->src_reg;
bool accesses_stack_only;
u8 b2 = 0, b3 = 0;
u8 *start_of_ldx;
s64 jmp_offset;
s32 insn_off;
int insn_idx;
u8 jmp_cond;
u8 *func;
int nops;
Expand All @@ -1831,6 +2000,10 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
EMIT_ENDBR();

ip = image + addrs[i - 1] + (prog - temp);
insn_idx = i - 1 + bpf_prog->aux->subprog_start;
accesses_stack_only =
env ? !env->insn_aux_data[insn_idx].non_stack_access :
false;

switch (insn->code) {
/* ALU */
Expand Down Expand Up @@ -2207,49 +2380,17 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
EMIT_LFENCE();
break;

/* ST: *(u8*)(dst_reg + off) = imm */
case BPF_ST | BPF_MEM | BPF_B:
if (is_ereg(dst_reg))
EMIT2(0x41, 0xC6);
else
EMIT1(0xC6);
goto st;
case BPF_ST | BPF_MEM | BPF_H:
if (is_ereg(dst_reg))
EMIT3(0x66, 0x41, 0xC7);
else
EMIT2(0x66, 0xC7);
goto st;
case BPF_ST | BPF_MEM | BPF_W:
if (is_ereg(dst_reg))
EMIT2(0x41, 0xC7);
else
EMIT1(0xC7);
goto st;
case BPF_ST | BPF_MEM | BPF_DW:
if (dst_reg == BPF_REG_PARAMS && insn->off == -8) {
/* Arg 6: store immediate in r9 register */
emit_mov_imm64(&prog, X86_REG_R9, imm32 >> 31, (u32)imm32);
break;
}
EMIT2(add_1mod(0x48, dst_reg), 0xC7);

st: insn_off = insn->off;
if (dst_reg == BPF_REG_PARAMS) {
/*
* Args 7+: reverse BPF negative offsets to
* x86 positive rsp offsets.
* BPF off=-16 → [rsp+0], off=-24 → [rsp+8], ...
*/
insn_off = outgoing_arg_base - outgoing_rsp - insn_off - 16;
dst_reg = BPF_REG_FP;
}
if (is_imm8(insn_off))
EMIT2(add_1reg(0x40, dst_reg), insn_off);
else
EMIT1_off32(add_1reg(0x80, dst_reg), insn_off);
err = emit_kasan_check(env, &prog, dst_reg, insn, ip,
true, accesses_stack_only);
if (err)
return err;

EMIT(imm32, bpf_size_to_x86_bytes(BPF_SIZE(insn->code)));
emit_st(&prog, insn, dst_reg, outgoing_arg_base,
outgoing_rsp);
break;

/* STX: *(u8*)(dst_reg + off) = src_reg */
Expand All @@ -2267,6 +2408,10 @@ st: insn_off = insn->off;
insn_off = outgoing_arg_base - outgoing_rsp - insn_off - 16;
dst_reg = BPF_REG_FP;
}
err = emit_kasan_check(env, &prog, dst_reg, insn, ip,
true, accesses_stack_only);
if (err)
return err;
emit_stx(&prog, BPF_SIZE(insn->code), dst_reg, src_reg, insn_off);
break;

Expand Down Expand Up @@ -2428,6 +2573,12 @@ st: insn_off = insn->off;
/* populate jmp_offset for JAE above to jump to start_of_ldx */
start_of_ldx = prog;
end_of_jmp[-1] = start_of_ldx - end_of_jmp;
} else {
err = emit_kasan_check(env, &prog, src_reg,
insn, ip, false,
accesses_stack_only);
if (err)
return err;
}
if (BPF_MODE(insn->code) == BPF_PROBE_MEMSX ||
BPF_MODE(insn->code) == BPF_MEMSX)
Expand Down Expand Up @@ -2489,15 +2640,16 @@ st: insn_off = insn->off;
}
fallthrough;
case BPF_STX | BPF_ATOMIC | BPF_W:
case BPF_STX | BPF_ATOMIC | BPF_DW:
if (insn->imm == (BPF_AND | BPF_FETCH) ||
insn->imm == (BPF_OR | BPF_FETCH) ||
insn->imm == (BPF_XOR | BPF_FETCH)) {
bool is64 = BPF_SIZE(insn->code) == BPF_DW;
u32 real_src_reg = src_reg;
u32 real_dst_reg = dst_reg;
u8 *branch_target;

case BPF_STX | BPF_ATOMIC | BPF_DW: {
bool is64 = BPF_SIZE(insn->code) == BPF_DW;
u32 real_src_reg = src_reg;
u32 real_dst_reg = dst_reg;
u8 *branch_target;
bool is_atomic_fetch =
(insn->imm == (BPF_AND | BPF_FETCH) ||
insn->imm == (BPF_OR | BPF_FETCH) ||
insn->imm == (BPF_XOR | BPF_FETCH));
if (is_atomic_fetch) {
/*
* Can't be implemented with a single x86 insn.
* Need to do a CMPXCHG loop.
Expand All @@ -2510,7 +2662,17 @@ st: insn_off = insn->off;
if (dst_reg == BPF_REG_0)
real_dst_reg = BPF_REG_AX;

ip += 3;
}
if (!bpf_atomic_is_load_store(insn)) {
err = emit_kasan_check(env, &prog, real_dst_reg,
insn, ip, true,
accesses_stack_only);
if (err)
return err;
branch_target = prog;
}
if (is_atomic_fetch) {
/* Load old value */
emit_ldx(&prog, BPF_SIZE(insn->code),
BPF_REG_0, real_dst_reg, insn->off);
Expand Down Expand Up @@ -2542,15 +2704,16 @@ st: insn_off = insn->off;
}

if (bpf_atomic_is_load_store(insn))
err = emit_atomic_ld_st(&prog, insn->imm, dst_reg, src_reg,
insn->off, BPF_SIZE(insn->code));
err = emit_atomic_ld_st(env, &prog, insn, ip,
dst_reg, src_reg,
accesses_stack_only);
else
err = emit_atomic_rmw(&prog, insn->imm, dst_reg, src_reg,
insn->off, BPF_SIZE(insn->code));
if (err)
return err;
break;

}
case BPF_STX | BPF_PROBE_ATOMIC | BPF_B:
case BPF_STX | BPF_PROBE_ATOMIC | BPF_H:
if (!bpf_atomic_is_load_store(insn)) {
Expand Down
2 changes: 2 additions & 0 deletions include/linux/bpf_verifier.h
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,8 @@ struct bpf_insn_aux_data {
u16 const_reg_map_mask;
u16 const_reg_subprog_mask;
u32 const_reg_vals[10];
/* instruction can access non-stack memory */
bool non_stack_access;
};

#define MAX_USED_MAPS 64 /* max number of maps accessed by one eBPF program */
Expand Down
Loading