[PATCH] riscv: Fix auipc+jalr relocation range checks
Palmer Dabbelt
palmer at dabbelt.com
Fri Mar 11 09:00:25 PST 2022
On Wed, 23 Feb 2022 11:12:57 PST (-0800), kernel at esmil.dk wrote:
> 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 <kernel at esmil.dk>
> ---
> arch/riscv/kernel/module.c | 21 ++++++++++++++++-----
> 1 file changed, 16 insertions(+), 5 deletions(-)
>
> diff --git a/arch/riscv/kernel/module.c b/arch/riscv/kernel/module.c
> index 68a9e3d1fe16..4a48287513c3 100644
> --- a/arch/riscv/kernel/module.c
> +++ b/arch/riscv/kernel/module.c
> @@ -13,6 +13,19 @@
> #include <linux/pgtable.h>
> #include <asm/sections.h>
>
> +/*
> + * The auipc+jalr instruction pair can reach any PC-relative offset
> + * in the range [-2^31 - 2^11, 2^31 - 2^11)
> + */
> +static bool riscv_insn_valid_32bit_offset(ptrdiff_t val)
> +{
> +#ifdef CONFIG_32BIT
> + return true;
> +#else
> + return (-(1L << 31) - (1L << 11)) <= val && val < ((1L << 31) - (1L << 11));
> +#endif
> +}
> +
> static int apply_r_riscv_32_rela(struct module *me, u32 *location, Elf_Addr v)
> {
> if (v != (u32)v) {
> @@ -95,7 +108,7 @@ static int apply_r_riscv_pcrel_hi20_rela(struct module *me, u32 *location,
> ptrdiff_t offset = (void *)v - (void *)location;
> s32 hi20;
>
> - if (offset != (s32)offset) {
> + if (!riscv_insn_valid_32bit_offset(offset)) {
> pr_err(
> "%s: target %016llx can not be addressed by the 32-bit offset from PC = %p\n",
> me->name, (long long)v, location);
> @@ -197,10 +210,9 @@ static int apply_r_riscv_call_plt_rela(struct module *me, u32 *location,
> Elf_Addr v)
> {
> ptrdiff_t offset = (void *)v - (void *)location;
> - s32 fill_v = offset;
> u32 hi20, lo12;
>
> - if (offset != fill_v) {
> + if (!riscv_insn_valid_32bit_offset(offset)) {
> /* Only emit the plt entry if offset over 32-bit range */
> if (IS_ENABLED(CONFIG_MODULE_SECTIONS)) {
> offset = module_emit_plt_entry(me, v);
> @@ -224,10 +236,9 @@ static int apply_r_riscv_call_rela(struct module *me, u32 *location,
> Elf_Addr v)
> {
> ptrdiff_t offset = (void *)v - (void *)location;
> - s32 fill_v = offset;
> u32 hi20, lo12;
>
> - if (offset != fill_v) {
> + if (!riscv_insn_valid_32bit_offset(offset)) {
> pr_err(
> "%s: target %016llx can not be addressed by the 32-bit offset from PC = %p\n",
> me->name, (long long)v, location);
Thanks, this is on fixes.
More information about the linux-riscv
mailing list