[PATCH 2/5] riscv: kexec_file: Pair PC-relative low parts with their high parts

Han Gao via B4 Relay devnull+gaohan.iscas.ac.cn at kernel.org
Fri Oct 23 00:33:15 PDT 2026


From: Han Gao <gaohan at iscas.ac.cn>

The purgatory relocation code applies R_RISCV_PCREL_HI20 to the auipc
and to the instruction after it, and ignores R_RISCV_PCREL_LO12_I. This
assumes that the low part immediately follows its high part. Compilers
may schedule several auipc instructions before their users; clang does
so in sha256_init(), which corrupts the hash and leaves purgatory
spinning on a digest mismatch.

Apply the high part to the auipc only. Resolve each R_RISCV_PCREL_LO12_I
and R_RISCV_PCREL_LO12_S through the R_RISCV_PCREL_HI20 at the address
its symbol labels, as the module loader does.

Fixes: 838b3e28488f ("RISC-V: Load purgatory in kexec_file")
Signed-off-by: Han Gao <gaohan at iscas.ac.cn>
---
 arch/riscv/kernel/machine_kexec_file.c | 84 ++++++++++++++++++++++++++--------
 1 file changed, 66 insertions(+), 18 deletions(-)

diff --git a/arch/riscv/kernel/machine_kexec_file.c b/arch/riscv/kernel/machine_kexec_file.c
index 26cd2a8bd0cd..1d7636acd382 100644
--- a/arch/riscv/kernel/machine_kexec_file.c
+++ b/arch/riscv/kernel/machine_kexec_file.c
@@ -104,6 +104,8 @@ static char *setup_kdump_cmdline(struct kimage *image, char *cmdline,
 
 #define ENCODE_ITYPE_IMM(x) \
 	(RV_X(x, 0, 12) << 20)
+#define ENCODE_STYPE_IMM(x) \
+	((RV_X(x, 0, 5) << 7) | (RV_X(x, 5, 7) << 25))
 #define ENCODE_BTYPE_IMM(x) \
 	((RV_X(x, 1, 4) << 8) | (RV_X(x, 5, 6) << 25) | \
 	(RV_X(x, 11, 1) << 7) | (RV_X(x, 12, 1) << 31))
@@ -128,6 +130,49 @@ static char *setup_kdump_cmdline(struct kimage *image, char *cmdline,
 #define CLEAN_IMM(type, x) \
 	((~ENCODE_##type##_IMM((uint64_t)(-1))) & (x))
 
+static int riscv_kexec_rela_value(const struct purgatory_info *pi,
+				  const Elf_Shdr *symtab, const Elf64_Rela *rela,
+				  unsigned long *val)
+{
+	const Elf_Sym *sym = (void *)pi->ehdr + symtab->sh_offset;
+
+	sym += ELF64_R_SYM(rela->r_info);
+	if (sym->st_shndx == SHN_ABS)
+		*val = 0;
+	else if (sym->st_shndx < pi->ehdr->e_shnum)
+		*val = pi->sechdrs[sym->st_shndx].sh_addr;
+	else
+		return -ENOEXEC;
+	*val += sym->st_value + rela->r_addend;
+	return 0;
+}
+
+/*
+ * An R_RISCV_PCREL_LO12_* symbol labels its R_RISCV_PCREL_HI20 instruction,
+ * which compilers need not place immediately before the low part.
+ */
+static int riscv_kexec_pcrel_hi20(const struct purgatory_info *pi,
+				  const Elf_Shdr *section, const Elf_Shdr *relsec,
+				  const Elf_Shdr *symtab, unsigned long hi_addr,
+				  long *offset)
+{
+	const Elf64_Rela *relas = (void *)pi->ehdr + relsec->sh_offset;
+	unsigned long val;
+	int i, ret;
+
+	for (i = 0; i < relsec->sh_size / sizeof(*relas); i++) {
+		if (ELF64_R_TYPE(relas[i].r_info) != R_RISCV_PCREL_HI20 ||
+		    section->sh_addr + relas[i].r_offset != hi_addr)
+			continue;
+		ret = riscv_kexec_rela_value(pi, symtab, &relas[i], &val);
+		if (ret)
+			return ret;
+		*offset = val - hi_addr;
+		return 0;
+	}
+	return -ENOEXEC;
+}
+
 int arch_kexec_apply_relocations_add(struct purgatory_info *pi,
 				     Elf_Shdr *section,
 				     const Elf_Shdr *relsec,
@@ -149,8 +194,8 @@ int arch_kexec_apply_relocations_add(struct purgatory_info *pi,
 		const Elf_Sym *sym;	/* symbol to relocate */
 		unsigned long addr;	/* final location after relocation */
 		unsigned long val;	/* relocated symbol value */
-		unsigned long sec_base;	/* relocated symbol value */
 		void *loc;		/* tmp location to modify */
+		long offset;
 
 		sym = (void *)pi->ehdr + symtab->sh_offset;
 		sym += ELF64_R_SYM(relas[i].r_info);
@@ -164,18 +209,11 @@ int arch_kexec_apply_relocations_add(struct purgatory_info *pi,
 		loc += section->sh_offset;
 		loc += relas[i].r_offset;
 
-		if (sym->st_shndx == SHN_ABS)
-			sec_base = 0;
-		else if (sym->st_shndx >= pi->ehdr->e_shnum) {
+		if (riscv_kexec_rela_value(pi, symtab, &relas[i], &val)) {
 			pr_err("Invalid section %d for symbol %s\n",
 			       sym->st_shndx, name);
 			return -ENOEXEC;
-		} else
-			sec_base = pi->sechdrs[sym->st_shndx].sh_addr;
-
-		val = sym->st_value;
-		val += sec_base;
-		val += relas[i].r_addend;
+		}
 
 		addr = section->sh_addr + relas[i].r_offset;
 
@@ -190,13 +228,25 @@ int arch_kexec_apply_relocations_add(struct purgatory_info *pi,
 			*(u32 *)loc = CLEAN_IMM(JTYPE, *(u32 *)loc) |
 				 ENCODE_JTYPE_IMM(val - addr);
 			break;
-		/*
-		 * With no R_RISCV_PCREL_LO12_S, R_RISCV_PCREL_LO12_I
-		 * sym is expected to be next to R_RISCV_PCREL_HI20
-		 * in purgatory relsec. Handle it like R_RISCV_CALL
-		 * sym, instead of searching the whole relsec.
-		 */
 		case R_RISCV_PCREL_HI20:
+			*(u32 *)loc = CLEAN_IMM(UTYPE, *(u32 *)loc) |
+				 ENCODE_UTYPE_IMM(RISCV_CONST_HIGH_PART(val - addr));
+			break;
+		case R_RISCV_PCREL_LO12_I:
+		case R_RISCV_PCREL_LO12_S:
+			if (riscv_kexec_pcrel_hi20(pi, section, relsec, symtab,
+						   val, &offset)) {
+				pr_err("No R_RISCV_PCREL_HI20 for %s\n", name);
+				return -ENOEXEC;
+			}
+			offset = RISCV_CONST_LOW_PART(offset);
+			if (r_type == R_RISCV_PCREL_LO12_I)
+				*(u32 *)loc = CLEAN_IMM(ITYPE, *(u32 *)loc) |
+					 ENCODE_ITYPE_IMM(offset);
+			else
+				*(u32 *)loc = CLEAN_IMM(STYPE, *(u32 *)loc) |
+					 ENCODE_STYPE_IMM(offset);
+			break;
 		case R_RISCV_CALL_PLT:
 		case R_RISCV_CALL:
 			*(u64 *)loc = CLEAN_IMM(UITYPE, *(u64 *)loc) |
@@ -222,8 +272,6 @@ int arch_kexec_apply_relocations_add(struct purgatory_info *pi,
 		case R_RISCV_SUB32:
 			*(u32 *)loc -= val;
 			break;
-		/* It has been applied by R_RISCV_PCREL_HI20 sym */
-		case R_RISCV_PCREL_LO12_I:
 		case R_RISCV_ALIGN:
 		case R_RISCV_RELAX:
 			break;

-- 
2.55.0





More information about the kexec mailing list