Skip to content
Closed
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
18 changes: 18 additions & 0 deletions arch/riscv/include/asm/insn.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@
#define RV_INSN_OPCODE_MASK GENMASK(6, 0)
#define RV_INSN_OPCODE_OPOFF 0
#define RV_INSN_FUNCT12_OPOFF 20
#define RVG_FUNCT5_MASK GENMASK(31, 27)
#define RVG_FUNCT5_OPOFF 27

#define RV_ENCODE_FUNCT3(f_) (RVG_FUNCT3_##f_ << RV_INSN_FUNCT3_OPOFF)
#define RV_ENCODE_FUNCT12(f_) (RVG_FUNCT12_##f_ << RV_INSN_FUNCT12_OPOFF)
#define RV_ENCODE_FUNCT5(f_) (RVG_FUNCT5_##f_ << RVG_FUNCT5_OPOFF)

/* The bit field of immediate value in I-type instruction */
#define RV_I_IMM_SIGN_OPOFF 31
Expand Down Expand Up @@ -137,6 +140,7 @@
/* parts of opcode for RVG*/
#define RVG_OPCODE_FENCE 0x0f
#define RVG_OPCODE_AUIPC 0x17
#define RVG_OPCODE_LRSC 0x2f
#define RVG_OPCODE_BRANCH 0x63
#define RVG_OPCODE_JALR 0x67
#define RVG_OPCODE_JAL 0x6f
Expand Down Expand Up @@ -176,6 +180,9 @@
#define RVG_FUNCT3_BLTU 0x6
#define RVG_FUNCT3_BGEU 0x7

#define RVG_FUNCT5_LR 0x02
#define RVG_FUNCT5_SC 0x03

/* parts of funct3 code for C extension*/
#define RVC_FUNCT3_C_BEQZ 0x6
#define RVC_FUNCT3_C_BNEZ 0x7
Expand All @@ -200,6 +207,8 @@
#define RVG_MATCH_BGEU (RV_ENCODE_FUNCT3(BGEU) | RVG_OPCODE_BRANCH)
#define RVG_MATCH_EBREAK (RV_ENCODE_FUNCT12(EBREAK) | RVG_OPCODE_SYSTEM)
#define RVG_MATCH_SRET (RV_ENCODE_FUNCT12(SRET) | RVG_OPCODE_SYSTEM)
#define RVG_MATCH_LR (RV_ENCODE_FUNCT5(LR) | RVG_OPCODE_LRSC)
#define RVG_MATCH_SC (RV_ENCODE_FUNCT5(SC) | RVG_OPCODE_LRSC)
#define RVC_MATCH_C_BEQZ (RVC_ENCODE_FUNCT3(C_BEQZ) | RVC_OPCODE_C1)
#define RVC_MATCH_C_BNEZ (RVC_ENCODE_FUNCT3(C_BNEZ) | RVC_OPCODE_C1)
#define RVC_MATCH_C_J (RVC_ENCODE_FUNCT3(C_J) | RVC_OPCODE_C1)
Expand Down Expand Up @@ -227,6 +236,8 @@
#define RVC_MASK_C_EBREAK 0xffff
#define RVG_MASK_EBREAK 0xffffffff
#define RVG_MASK_SRET 0xffffffff
#define RVG_MASK_LR (RVG_FUNCT5_MASK | GENMASK(14, 14) | RV_INSN_OPCODE_MASK)
#define RVG_MASK_SC (RVG_FUNCT5_MASK | GENMASK(14, 14) | RV_INSN_OPCODE_MASK)

#define __INSN_LENGTH_MASK _UL(0x3)
#define __INSN_LENGTH_GE_32 _UL(0x3)
Expand Down Expand Up @@ -262,6 +273,13 @@ __RISCV_INSN_FUNCS(c_ebreak, RVC_MASK_C_EBREAK, RVC_MATCH_C_EBREAK)
__RISCV_INSN_FUNCS(ebreak, RVG_MASK_EBREAK, RVG_MATCH_EBREAK)
__RISCV_INSN_FUNCS(sret, RVG_MASK_SRET, RVG_MATCH_SRET)
__RISCV_INSN_FUNCS(fence, RVG_MASK_FENCE, RVG_MATCH_FENCE);
/*
* LR/SC (Zalrsc, opcode 0x2f). funct3 selects the operand size: 000 (W)
* and 011 (D) on RV64; bit 14 is clear for both, so it is used to match
* either size. The .aq/.rl bits (26:25) and rd are ignored.
*/
__RISCV_INSN_FUNCS(lr, RVG_MASK_LR, RVG_MATCH_LR)
__RISCV_INSN_FUNCS(sc, RVG_MASK_SC, RVG_MATCH_SC)

/* special case to catch _any_ system instruction */
static __always_inline bool riscv_insn_is_system(u32 code)
Expand Down
2 changes: 2 additions & 0 deletions arch/riscv/kernel/probes/decode-insn.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ riscv_probe_decode_insn(probe_opcode_t *addr, struct arch_probe_insn *api)
*/
RISCV_INSN_REJECTED(system, insn);
RISCV_INSN_REJECTED(fence, insn);
RISCV_INSN_REJECTED(lr, insn);
RISCV_INSN_REJECTED(sc, insn);

/*
* Simulate instructions list:
Expand Down
59 changes: 59 additions & 0 deletions arch/riscv/kernel/probes/kprobes.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <asm/cacheflush.h>
#include <asm/bug.h>
#include <asm/text-patching.h>
#include <asm/insn.h>

#include "decode-insn.h"

Expand Down Expand Up @@ -69,6 +70,61 @@ static bool __kprobes arch_check_kprobe(unsigned long addr)
return false;
}

/*
* A trap taken in the middle of an LR/SC sequence clears the load
* reservation, so an SC following the probed instruction would always
* fail and the enclosing retry loop would re-enter the breakpoint.
* Reject probes inside such a sequence.
*
* A constrained LR/SC loop (Zalrsc) must be contained within a 64-byte
* contiguous region of memory and comprise at most 16 instructions.
* Both bounds hold regardless of the C extension, so scanning back
* 64 bytes from the probed instruction always covers the enclosing
* sequence.
*/
#define MAX_ATOMIC_CONTEXT_SIZE 64

static bool __kprobes riscv_probe_insn_in_atomic(unsigned long addr)
{
unsigned long tmp, offset, scan_start;
bool in_atomic = false;

if (!kallsyms_lookup_size_offset(addr, NULL, &offset))
return false;

tmp = addr - offset; /* function entry */

if (offset > MAX_ATOMIC_CONTEXT_SIZE)
scan_start = addr - MAX_ATOMIC_CONTEXT_SIZE;
else
scan_start = tmp;

/*
* scan_start is not necessarily an instruction boundary, so walk
* forward from the function entry to the first instruction start
* at or after scan_start. Losing at most one instruction of
* coverage this way still leaves a window wide enough to contain
* any constrained LR/SC loop.
*/
while (tmp < scan_start)
tmp += GET_INSN_LENGTH(*(u16 *)tmp);

/* scan the window, tracking whether an LR is still outstanding */
while (tmp < addr) {
u32 insn = *(u32 *)tmp;

if (GET_INSN_LENGTH(insn) == 4) {
if (riscv_insn_is_lr(insn))
in_atomic = true;
else if (riscv_insn_is_sc(insn))
in_atomic = false;
}
tmp += GET_INSN_LENGTH(insn);
}

return in_atomic;
}

int __kprobes arch_prepare_kprobe(struct kprobe *p)
{
u16 *insn = (u16 *)p->addr;
Expand All @@ -79,6 +135,9 @@ int __kprobes arch_prepare_kprobe(struct kprobe *p)
if (!arch_check_kprobe((unsigned long)p->addr))
return -EILSEQ;

if (riscv_probe_insn_in_atomic((unsigned long)p->addr))
return -EINVAL;

/* copy instruction */
p->opcode = (kprobe_opcode_t)(*insn++);
if (GET_INSN_LENGTH(p->opcode) == 4)
Expand Down