Skip to content

Commit 0966d38

Browse files
esmilpalmer-dabbelt
authored andcommitted
riscv: Fix auipc+jalr relocation range checks
RISC-V can do PC-relative jumps with a 32bit range using the following two instructions: auipc t0, imm20 ; t0 = PC + imm20 * 2^12 jalr ra, t0, imm12 ; ra = PC + 4, PC = t0 + imm12 Crucially both the 20bit immediate imm20 and the 12bit immediate imm12 are treated as two's-complement signed values. For this reason the immediates are usually calculated like this: imm20 = (offset + 0x800) >> 12 imm12 = offset & 0xfff ..where offset is the signed offset from the auipc instruction. When the 11th bit of offset is 0 the addition of 0x800 doesn't change the top 20 bits and imm12 considered positive. When the 11th bit is 1 the carry of the addition by 0x800 means imm20 is one higher, but since imm12 is then considered negative the two's complement representation means it all cancels out nicely. However, this addition by 0x800 (2^11) means an offset greater than or equal to 2^31 - 2^11 would overflow so imm20 is considered negative and result in a backwards jump. Similarly the lower range of offset is also moved down by 2^11 and hence the true 32bit range is [-2^31 - 2^11, 2^31 - 2^11) Signed-off-by: Emil Renner Berthing <[email protected]> Fixes: e2c0cdf ("RISC-V: User-facing API") Cc: [email protected] Signed-off-by: Palmer Dabbelt <[email protected]>
1 parent c80ee64 commit 0966d38

File tree

1 file changed

+16
-5
lines changed

1 file changed

+16
-5
lines changed

arch/riscv/kernel/module.c

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,19 @@
1313
#include <linux/pgtable.h>
1414
#include <asm/sections.h>
1515

16+
/*
17+
* The auipc+jalr instruction pair can reach any PC-relative offset
18+
* in the range [-2^31 - 2^11, 2^31 - 2^11)
19+
*/
20+
static bool riscv_insn_valid_32bit_offset(ptrdiff_t val)
21+
{
22+
#ifdef CONFIG_32BIT
23+
return true;
24+
#else
25+
return (-(1L << 31) - (1L << 11)) <= val && val < ((1L << 31) - (1L << 11));
26+
#endif
27+
}
28+
1629
static int apply_r_riscv_32_rela(struct module *me, u32 *location, Elf_Addr v)
1730
{
1831
if (v != (u32)v) {
@@ -95,7 +108,7 @@ static int apply_r_riscv_pcrel_hi20_rela(struct module *me, u32 *location,
95108
ptrdiff_t offset = (void *)v - (void *)location;
96109
s32 hi20;
97110

98-
if (offset != (s32)offset) {
111+
if (!riscv_insn_valid_32bit_offset(offset)) {
99112
pr_err(
100113
"%s: target %016llx can not be addressed by the 32-bit offset from PC = %p\n",
101114
me->name, (long long)v, location);
@@ -197,10 +210,9 @@ static int apply_r_riscv_call_plt_rela(struct module *me, u32 *location,
197210
Elf_Addr v)
198211
{
199212
ptrdiff_t offset = (void *)v - (void *)location;
200-
s32 fill_v = offset;
201213
u32 hi20, lo12;
202214

203-
if (offset != fill_v) {
215+
if (!riscv_insn_valid_32bit_offset(offset)) {
204216
/* Only emit the plt entry if offset over 32-bit range */
205217
if (IS_ENABLED(CONFIG_MODULE_SECTIONS)) {
206218
offset = module_emit_plt_entry(me, v);
@@ -224,10 +236,9 @@ static int apply_r_riscv_call_rela(struct module *me, u32 *location,
224236
Elf_Addr v)
225237
{
226238
ptrdiff_t offset = (void *)v - (void *)location;
227-
s32 fill_v = offset;
228239
u32 hi20, lo12;
229240

230-
if (offset != fill_v) {
241+
if (!riscv_insn_valid_32bit_offset(offset)) {
231242
pr_err(
232243
"%s: target %016llx can not be addressed by the 32-bit offset from PC = %p\n",
233244
me->name, (long long)v, location);

0 commit comments

Comments
 (0)