Skip to content

Commit bd55e79

Browse files
rnavmpe
authored andcommitted
powerpc/module_64: Use special stub for _mcount() with -mprofile-kernel
Since commit c55d7b5 ("powerpc: Remove STRICT_KERNEL_RWX incompatibility with RELOCATABLE"), powerpc kernels with -mprofile-kernel can crash in certain scenarios with a trace like below: BUG: Unable to handle kernel instruction fetch (NULL pointer?) Faulting instruction address: 0x00000000 Oops: Kernel access of bad area, sig: 11 [#1] LE PAGE_SIZE=64K MMU=Radix SMP NR_CPUS=256 DEBUG_PAGEALLOC NUMA PowerNV <snip> NIP [0000000000000000] 0x0 LR [c0080000102c0048] ext4_iomap_end+0x8/0x30 [ext4] Call Trace: iomap_apply+0x20c/0x920 (unreliable) iomap_bmap+0xfc/0x160 ext4_bmap+0xa4/0x180 [ext4] bmap+0x4c/0x80 jbd2_journal_init_inode+0x44/0x1a0 [jbd2] ext4_load_journal+0x440/0x860 [ext4] ext4_fill_super+0x342c/0x3ab0 [ext4] mount_bdev+0x25c/0x290 ext4_mount+0x28/0x50 [ext4] legacy_get_tree+0x4c/0xb0 vfs_get_tree+0x4c/0x130 do_mount+0xa18/0xc50 sys_mount+0x158/0x180 system_call+0x5c/0x68 The NIP points to NULL, or a random location (data even), while the LR always points to the LEP of a function (with an offset of 8), indicating that something went wrong with ftrace. However, ftrace is not necessarily active when such crashes occur. The kernel OOPS sometimes follows a warning from ftrace indicating that some module functions could not be patched with a nop. Other times, if a module is loaded early during boot, instruction patching can fail due to a separate bug, but the error is not reported due to missing error reporting. In all the above cases when instruction patching fails, ftrace will be disabled but certain kernel module functions will be left with default calls to _mcount(). This is not a problem with ELFv1. However, with -mprofile-kernel, the default stub is problematic since it depends on a valid module TOC in r2. If the kernel (or a different module) calls into a function that does not use the TOC, the function won't have a prologue to setup the module TOC. When that function calls into _mcount(), we will end up in the relocation stub that will use the previous TOC, and end up trying to jump into a random location. From the above trace: iomap_apply+0x20c/0x920 [kernel TOC] | V ext4_iomap_end+0x8/0x30 [no GEP == kernel TOC] | V _mcount() stub [uses kernel TOC -> random entry] To address this, let's change over to using the special stub that is used for ftrace_[regs_]caller() for _mcount(). This ensures that we are not dependent on a valid module TOC in r2 for default _mcount() handling. Reported-by: Qian Cai <[email protected]> Signed-off-by: Naveen N. Rao <[email protected]> Tested-by: Qian Cai <[email protected]> Signed-off-by: Michael Ellerman <[email protected]> Link: https://lore.kernel.org/r/8affd4298d22099bbd82544fab8185700a6222b1.1587488954.git.naveen.n.rao@linux.vnet.ibm.com
1 parent 1f2aaed commit bd55e79

File tree

1 file changed

+104
-118
lines changed

1 file changed

+104
-118
lines changed

arch/powerpc/kernel/module_64.c

Lines changed: 104 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,92 @@ int module_frob_arch_sections(Elf64_Ehdr *hdr,
334334
return 0;
335335
}
336336

337+
#ifdef CONFIG_MPROFILE_KERNEL
338+
339+
#define PACATOC offsetof(struct paca_struct, kernel_toc)
340+
341+
/*
342+
* ld r12,PACATOC(r13)
343+
* addis r12,r12,<high>
344+
* addi r12,r12,<low>
345+
* mtctr r12
346+
* bctr
347+
*/
348+
static u32 stub_insns[] = {
349+
PPC_INST_LD | __PPC_RT(R12) | __PPC_RA(R13) | PACATOC,
350+
PPC_INST_ADDIS | __PPC_RT(R12) | __PPC_RA(R12),
351+
PPC_INST_ADDI | __PPC_RT(R12) | __PPC_RA(R12),
352+
PPC_INST_MTCTR | __PPC_RS(R12),
353+
PPC_INST_BCTR,
354+
};
355+
356+
/*
357+
* For mprofile-kernel we use a special stub for ftrace_caller() because we
358+
* can't rely on r2 containing this module's TOC when we enter the stub.
359+
*
360+
* That can happen if the function calling us didn't need to use the toc. In
361+
* that case it won't have setup r2, and the r2 value will be either the
362+
* kernel's toc, or possibly another modules toc.
363+
*
364+
* To deal with that this stub uses the kernel toc, which is always accessible
365+
* via the paca (in r13). The target (ftrace_caller()) is responsible for
366+
* saving and restoring the toc before returning.
367+
*/
368+
static inline int create_ftrace_stub(struct ppc64_stub_entry *entry,
369+
unsigned long addr,
370+
struct module *me)
371+
{
372+
long reladdr;
373+
374+
memcpy(entry->jump, stub_insns, sizeof(stub_insns));
375+
376+
/* Stub uses address relative to kernel toc (from the paca) */
377+
reladdr = addr - kernel_toc_addr();
378+
if (reladdr > 0x7FFFFFFF || reladdr < -(0x80000000L)) {
379+
pr_err("%s: Address of %ps out of range of kernel_toc.\n",
380+
me->name, (void *)addr);
381+
return 0;
382+
}
383+
384+
entry->jump[1] |= PPC_HA(reladdr);
385+
entry->jump[2] |= PPC_LO(reladdr);
386+
387+
/* Eventhough we don't use funcdata in the stub, it's needed elsewhere. */
388+
entry->funcdata = func_desc(addr);
389+
entry->magic = STUB_MAGIC;
390+
391+
return 1;
392+
}
393+
394+
static bool is_mprofile_ftrace_call(const char *name)
395+
{
396+
if (!strcmp("_mcount", name))
397+
return true;
398+
#ifdef CONFIG_DYNAMIC_FTRACE
399+
if (!strcmp("ftrace_caller", name))
400+
return true;
401+
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_REGS
402+
if (!strcmp("ftrace_regs_caller", name))
403+
return true;
404+
#endif
405+
#endif
406+
407+
return false;
408+
}
409+
#else
410+
static inline int create_ftrace_stub(struct ppc64_stub_entry *entry,
411+
unsigned long addr,
412+
struct module *me)
413+
{
414+
return 0;
415+
}
416+
417+
static bool is_mprofile_ftrace_call(const char *name)
418+
{
419+
return false;
420+
}
421+
#endif
422+
337423
/*
338424
* r2 is the TOC pointer: it actually points 0x8000 into the TOC (this gives the
339425
* value maximum span in an instruction which uses a signed offset). Round down
@@ -349,10 +435,14 @@ static inline unsigned long my_r2(const Elf64_Shdr *sechdrs, struct module *me)
349435
static inline int create_stub(const Elf64_Shdr *sechdrs,
350436
struct ppc64_stub_entry *entry,
351437
unsigned long addr,
352-
struct module *me)
438+
struct module *me,
439+
const char *name)
353440
{
354441
long reladdr;
355442

443+
if (is_mprofile_ftrace_call(name))
444+
return create_ftrace_stub(entry, addr, me);
445+
356446
memcpy(entry->jump, ppc64_stub_insns, sizeof(ppc64_stub_insns));
357447

358448
/* Stub uses address relative to r2. */
@@ -376,7 +466,8 @@ static inline int create_stub(const Elf64_Shdr *sechdrs,
376466
stub to set up the TOC ptr (r2) for the function. */
377467
static unsigned long stub_for_addr(const Elf64_Shdr *sechdrs,
378468
unsigned long addr,
379-
struct module *me)
469+
struct module *me,
470+
const char *name)
380471
{
381472
struct ppc64_stub_entry *stubs;
382473
unsigned int i, num_stubs;
@@ -393,45 +484,12 @@ static unsigned long stub_for_addr(const Elf64_Shdr *sechdrs,
393484
return (unsigned long)&stubs[i];
394485
}
395486

396-
if (!create_stub(sechdrs, &stubs[i], addr, me))
487+
if (!create_stub(sechdrs, &stubs[i], addr, me, name))
397488
return 0;
398489

399490
return (unsigned long)&stubs[i];
400491
}
401492

402-
#ifdef CONFIG_MPROFILE_KERNEL
403-
static bool is_mprofile_ftrace_call(const char *name)
404-
{
405-
if (!strcmp("_mcount", name))
406-
return true;
407-
408-
return false;
409-
}
410-
411-
/*
412-
* In case of _mcount calls, do not save the current callee's TOC (in r2) into
413-
* the original caller's stack frame. If we did we would clobber the saved TOC
414-
* value of the original caller.
415-
*/
416-
static void squash_toc_save_inst(const char *name, unsigned long addr)
417-
{
418-
struct ppc64_stub_entry *stub = (struct ppc64_stub_entry *)addr;
419-
420-
/* Only for calls to _mcount */
421-
if (strcmp("_mcount", name) != 0)
422-
return;
423-
424-
stub->jump[2] = PPC_INST_NOP;
425-
}
426-
#else
427-
static void squash_toc_save_inst(const char *name, unsigned long addr) { }
428-
429-
static bool is_mprofile_ftrace_call(const char *name)
430-
{
431-
return false;
432-
}
433-
#endif
434-
435493
/* We expect a noop next: if it is, replace it with instruction to
436494
restore r2. */
437495
static int restore_r2(const char *name, u32 *instruction, struct module *me)
@@ -576,14 +634,13 @@ int apply_relocate_add(Elf64_Shdr *sechdrs,
576634
if (sym->st_shndx == SHN_UNDEF ||
577635
sym->st_shndx == SHN_LIVEPATCH) {
578636
/* External: go via stub */
579-
value = stub_for_addr(sechdrs, value, me);
637+
value = stub_for_addr(sechdrs, value, me,
638+
strtab + sym->st_name);
580639
if (!value)
581640
return -ENOENT;
582641
if (!restore_r2(strtab + sym->st_name,
583642
(u32 *)location + 1, me))
584643
return -ENOEXEC;
585-
586-
squash_toc_save_inst(strtab + sym->st_name, value);
587644
} else
588645
value += local_entry_offset(sym);
589646

@@ -719,88 +776,17 @@ int module_trampoline_target(struct module *mod, unsigned long addr,
719776
return 0;
720777
}
721778

722-
#ifdef CONFIG_MPROFILE_KERNEL
723-
724-
#define PACATOC offsetof(struct paca_struct, kernel_toc)
725-
726-
/*
727-
* For mprofile-kernel we use a special stub for ftrace_caller() because we
728-
* can't rely on r2 containing this module's TOC when we enter the stub.
729-
*
730-
* That can happen if the function calling us didn't need to use the toc. In
731-
* that case it won't have setup r2, and the r2 value will be either the
732-
* kernel's toc, or possibly another modules toc.
733-
*
734-
* To deal with that this stub uses the kernel toc, which is always accessible
735-
* via the paca (in r13). The target (ftrace_caller()) is responsible for
736-
* saving and restoring the toc before returning.
737-
*/
738-
static unsigned long create_ftrace_stub(const Elf64_Shdr *sechdrs,
739-
struct module *me, unsigned long addr)
740-
{
741-
struct ppc64_stub_entry *entry;
742-
unsigned int i, num_stubs;
743-
/*
744-
* ld r12,PACATOC(r13)
745-
* addis r12,r12,<high>
746-
* addi r12,r12,<low>
747-
* mtctr r12
748-
* bctr
749-
*/
750-
static u32 stub_insns[] = {
751-
PPC_INST_LD | __PPC_RT(R12) | __PPC_RA(R13) | PACATOC,
752-
PPC_INST_ADDIS | __PPC_RT(R12) | __PPC_RA(R12),
753-
PPC_INST_ADDI | __PPC_RT(R12) | __PPC_RA(R12),
754-
PPC_INST_MTCTR | __PPC_RS(R12),
755-
PPC_INST_BCTR,
756-
};
757-
long reladdr;
758-
759-
num_stubs = sechdrs[me->arch.stubs_section].sh_size / sizeof(*entry);
760-
761-
/* Find the next available stub entry */
762-
entry = (void *)sechdrs[me->arch.stubs_section].sh_addr;
763-
for (i = 0; i < num_stubs && stub_func_addr(entry->funcdata); i++, entry++);
764-
765-
if (i >= num_stubs) {
766-
pr_err("%s: Unable to find a free slot for ftrace stub.\n", me->name);
767-
return 0;
768-
}
769-
770-
memcpy(entry->jump, stub_insns, sizeof(stub_insns));
771-
772-
/* Stub uses address relative to kernel toc (from the paca) */
773-
reladdr = addr - kernel_toc_addr();
774-
if (reladdr > 0x7FFFFFFF || reladdr < -(0x80000000L)) {
775-
pr_err("%s: Address of %ps out of range of kernel_toc.\n",
776-
me->name, (void *)addr);
777-
return 0;
778-
}
779-
780-
entry->jump[1] |= PPC_HA(reladdr);
781-
entry->jump[2] |= PPC_LO(reladdr);
782-
783-
/* Eventhough we don't use funcdata in the stub, it's needed elsewhere. */
784-
entry->funcdata = func_desc(addr);
785-
entry->magic = STUB_MAGIC;
786-
787-
return (unsigned long)entry;
788-
}
789-
#else
790-
static unsigned long create_ftrace_stub(const Elf64_Shdr *sechdrs,
791-
struct module *me, unsigned long addr)
792-
{
793-
return stub_for_addr(sechdrs, addr, me);
794-
}
795-
#endif
796-
797779
int module_finalize_ftrace(struct module *mod, const Elf_Shdr *sechdrs)
798780
{
799-
mod->arch.tramp = create_ftrace_stub(sechdrs, mod,
800-
(unsigned long)ftrace_caller);
781+
mod->arch.tramp = stub_for_addr(sechdrs,
782+
(unsigned long)ftrace_caller,
783+
mod,
784+
"ftrace_caller");
801785
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_REGS
802-
mod->arch.tramp_regs = create_ftrace_stub(sechdrs, mod,
803-
(unsigned long)ftrace_regs_caller);
786+
mod->arch.tramp_regs = stub_for_addr(sechdrs,
787+
(unsigned long)ftrace_regs_caller,
788+
mod,
789+
"ftrace_regs_caller");
804790
if (!mod->arch.tramp_regs)
805791
return -ENOENT;
806792
#endif

0 commit comments

Comments
 (0)