powerpc/kasan: fix r2 corruption in mem*() on GENERIC_ENTRY with old compilers

commit bee25f97ad ("powerpc: Enable GENERIC_ENTRY feature") introduced
a bug that, when CONFIG_CC_HAS_KASAN_MEMINTRINSIC_PREFIX is not
set, expand to two consecutive ELFv2 global entry points for the
same function body:

  _GLOBAL_TOC_KASAN(memcpy) expands to:
    _GLOBAL_TOC(memcpy);
    _GLOBAL_TOC(__memcpy)

On powerpc64le this produces:

  memcpy:
    addis r2,r12,(.TOC.-0b)@ha     <- global entry, sets up r2
    addi  r2,r2,(.TOC.-0b)@l
    .localentry memcpy,.-memcpy    <- local entry at memcpy+8
  __memcpy:
    addis r2,r12,(.TOC.-0b)@ha    <- memcpy+8: __memcpy global entry
    addi  r2,r2,(.TOC.-0b)@l
    .localentry __memcpy,.-__memcpy

The assembler sets memcpy's st_other field so its local entry offset is
8 bytes past the global entry point.  The linker resolves same-TOC
calls to the local entry, so every same-TOC caller of memcpy() lands
at memcpy+8 — which is the beginning of the __memcpy global-entry TOC
prologue.  That prologue uses r12 as the base to recompute r2, but r12
holds whatever the caller left in it (not the address of memcpy+8).
The result is r2 pointing at garbage on return.  Same-TOC callers do
not reload r2 after the call, so the first TOC-relative access after
returning from memcpy() or memmove() crashes or silently corrupts data.

With GENERIC_ENTRY selected, mm/kasan/shadow.c no longer provides its
own memcpy()/memmove()/memset() wrappers, so on a toolchain without
-mllvm -asan-kernel-mem-intrinsic-prefix=1 (e.g. GCC 9) every
instrumented file resolves mem*() directly to the assembly symbols and
hits this corrupt-r2 path.  This manifests as an early boot hang unique
to powerpc with GCC 9 and CONFIG_KASAN=y.

Fix this by removing the dual-entry macros entirely.  The assembly
files in mem_64.S, memcpy_64.S and copy_32.S now use plain
_GLOBAL()/_GLOBAL_TOC() for memset/memcpy/memmove, emitting a single
correct ELFv2 entry point.  The __mem* aliases required by mm/kasan
(used in mm/kasan/shadow.c and mm/kasan/generic.c as raw backends to
bypass KASAN checking) are provided entirely through the #define
redirections in asm/string.h, which already handles both the
CC_HAS_KASAN_MEMINTRINSIC_PREFIX and non-prefix cases in C.

As a consequence the _GLOBAL_KASAN(), _GLOBAL_TOC_KASAN() and
EXPORT_SYMBOL_KASAN() macros have no remaining users and are removed
from asm/kasan.h.  The has_renamed_memintrinsics() branch in
prom_init_check.sh, which conditionally whitelisted __memcpy/__memset
when CONFIG_CC_HAS_KASAN_MEMINTRINSIC_PREFIX was absent, is dead code
after this change and is removed; the whitelist now unconditionally
lists memcpy and memset.

Fixes: bee25f97ad ("powerpc: Enable GENERIC_ENTRY feature")
Reported-by: Venkat Rao Bagalkote <venkat88@linux.ibm.com>
Closes: https://lore.kernel.org/all/96dae110-f79b-4e54-8a9b-514369019b5d@linux.ibm.com
Tested-by: Venkat Rao Bagalkote <venkat88@linux.ibm.com>
Signed-off-by: Mukesh Kumar Chaurasiya (IBM) <mkchauras@gmail.com>
[Maddy: Fixed commit message to avoid checkpatch error]
Signed-off-by: Madhavan Srinivasan <maddy@linux.ibm.com>
Link: https://patch.msgid.link/20261003094006.3404731-1-mkchauras@gmail.com
This commit is contained in:
Mukesh Kumar Chaurasiya (IBM) 2026-10-03 15:10:06 +05:30 committed by Madhavan Srinivasan
parent 8fa763bc3e
commit de5a7fedf5
8 changed files with 13 additions and 83 deletions

View File

@ -2,21 +2,6 @@
#ifndef __ASM_KASAN_H
#define __ASM_KASAN_H
#if defined(CONFIG_KASAN) && !defined(CONFIG_CC_HAS_KASAN_MEMINTRINSIC_PREFIX)
#define _GLOBAL_KASAN(fn) \
_GLOBAL(fn); \
_GLOBAL(__##fn)
#define _GLOBAL_TOC_KASAN(fn) \
_GLOBAL_TOC(fn); \
_GLOBAL_TOC(__##fn)
#define EXPORT_SYMBOL_KASAN(fn) \
EXPORT_SYMBOL(__##fn)
#else /* CONFIG_KASAN && !CONFIG_CC_HAS_KASAN_MEMINTRINSIC_PREFIX */
#define _GLOBAL_KASAN(fn) _GLOBAL(fn)
#define _GLOBAL_TOC_KASAN(fn) _GLOBAL_TOC(fn)
#define EXPORT_SYMBOL_KASAN(fn)
#endif /* CONFIG_KASAN && !CONFIG_CC_HAS_KASAN_MEMINTRINSIC_PREFIX */
#ifndef __ASSEMBLER__
#include <asm/page.h>

View File

@ -29,29 +29,10 @@ extern void * memchr(const void *,int,__kernel_size_t);
void memcpy_flushcache(void *dest, const void *src, size_t size);
#ifdef CONFIG_KASAN
/* __mem variants are used by KASAN to implement instrumented meminstrinsics. */
#ifdef CONFIG_CC_HAS_KASAN_MEMINTRINSIC_PREFIX
/* Used by mm/kasan as raw backends to bypass KASAN checking. */
#define __memset memset
#define __memcpy memcpy
#define __memmove memmove
#else /* CONFIG_CC_HAS_KASAN_MEMINTRINSIC_PREFIX */
void *__memset(void *s, int c, __kernel_size_t count);
void *__memcpy(void *to, const void *from, __kernel_size_t n);
void *__memmove(void *to, const void *from, __kernel_size_t n);
#ifndef __SANITIZE_ADDRESS__
/*
* For files that are not instrumented (e.g. mm/slub.c) we
* should use not instrumented version of mem* functions.
*/
#define memcpy(dst, src, len) __memcpy(dst, src, len)
#define memmove(dst, src, len) __memmove(dst, src, len)
#define memset(s, c, n) __memset(s, c, n)
#ifndef __NO_FORTIFY
#define __NO_FORTIFY /* FORTIFY_SOURCE uses __builtin_memcpy, etc. */
#endif
#endif /* !__SANITIZE_ADDRESS__ */
#endif /* CONFIG_CC_HAS_KASAN_MEMINTRINSIC_PREFIX */
#endif /* CONFIG_KASAN */
#ifdef CONFIG_PPC64

View File

@ -35,12 +35,7 @@ void __init set_cur_cpu_spec(struct cpu_spec *s)
struct cpu_spec *t = &the_cpu_spec;
t = PTRRELOC(t);
/*
* use memcpy() instead of *t = *s so that GCC replaces it
* by __memcpy() when KASAN is active
*/
memcpy(t, s, sizeof(*t));
*t = *s;
*PTRRELOC(&cur_cpu_spec) = &the_cpu_spec;
}
@ -53,11 +48,8 @@ static struct cpu_spec * __init setup_cpu_spec(unsigned long offset,
t = PTRRELOC(t);
old = *t;
/*
* Copy everything, then do fixups. Use memcpy() instead of *t = *s
* so that GCC replaces it by __memcpy() when KASAN is active
*/
memcpy(t, s, sizeof(*t));
/* Copy everything, then do fixups. */
*t = *s;
/*
* If we are overriding a previous value derived from the real

View File

@ -1362,14 +1362,8 @@ static void __init prom_check_platform_support(void)
int prop_len = prom_getproplen(prom.chosen,
"ibm,arch-vec-5-platform-support");
/*
* First copy the architecture vec template
*
* use memcpy() instead of *vec = *vec_template so that GCC replaces it
* by __memcpy() when KASAN is active
*/
memcpy(&ibm_architecture_vec, &ibm_architecture_vec_template,
sizeof(ibm_architecture_vec));
/* First copy the architecture vec template */
ibm_architecture_vec = ibm_architecture_vec_template;
prom_strscpy_pad(ibm_architecture_vec.vec7.os_id, linux_banner, 256);

View File

@ -13,21 +13,8 @@
# If you really need to reference something from prom_init.o add
# it to the list below:
has_renamed_memintrinsics()
{
grep -q "^CONFIG_KASAN=y$" "${KCONFIG_CONFIG}" && \
! grep -q "^CONFIG_CC_HAS_KASAN_MEMINTRINSIC_PREFIX=y" "${KCONFIG_CONFIG}"
}
if has_renamed_memintrinsics
then
MEM_FUNCS="__memcpy __memset"
else
MEM_FUNCS="memcpy memset"
fi
WHITELIST="add_reloc_offset __bss_start __bss_stop copy_and_flush
_end enter_prom $MEM_FUNCS reloc_offset __secondary_hold
_end enter_prom memcpy memset reloc_offset __secondary_hold
__secondary_hold_acknowledge __secondary_hold_spinloop __start
logo_linux_clut224 btext_prepare_BAT
reloc_got2 kernstart_addr memstart_addr linux_banner _stext

View File

@ -10,7 +10,6 @@
#include <asm/errno.h>
#include <asm/ppc_asm.h>
#include <asm/code-patching-asm.h>
#include <asm/kasan.h>
#define COPY_16_BYTES \
lwz r7,4(r4); \
@ -87,7 +86,7 @@ EXPORT_SYMBOL(memset16)
* We therefore skip the optimised bloc that uses dcbz. This jump is
* replaced by a nop once cache is active. This is done in machine_init()
*/
_GLOBAL_KASAN(memset)
_GLOBAL(memset)
cmplwi 0,r5,4
blt 7f
@ -147,7 +146,6 @@ _GLOBAL_KASAN(memset)
bdnz 9b
blr
EXPORT_SYMBOL(memset)
EXPORT_SYMBOL_KASAN(memset)
/*
* This version uses dcbz on the complete cache lines in the
@ -160,12 +158,12 @@ EXPORT_SYMBOL_KASAN(memset)
* We therefore jump to generic_memcpy which doesn't use dcbz. This jump is
* replaced by a nop once cache is active. This is done in machine_init()
*/
_GLOBAL_KASAN(memmove)
_GLOBAL(memmove)
cmplw 0,r3,r4
bgt backwards_memcpy
/* fall through */
_GLOBAL_KASAN(memcpy)
_GLOBAL(memcpy)
1: b generic_memcpy
patch_site 1b, patch__memcpy_nocache
@ -241,8 +239,6 @@ _GLOBAL_KASAN(memcpy)
65: blr
EXPORT_SYMBOL(memcpy)
EXPORT_SYMBOL(memmove)
EXPORT_SYMBOL_KASAN(memcpy)
EXPORT_SYMBOL_KASAN(memmove)
generic_memcpy:
srwi. r7,r5,3

View File

@ -8,7 +8,6 @@
#include <asm/processor.h>
#include <asm/errno.h>
#include <asm/ppc_asm.h>
#include <asm/kasan.h>
#ifndef CONFIG_KASAN
_GLOBAL(__memset16)
@ -29,7 +28,7 @@ EXPORT_SYMBOL(__memset32)
EXPORT_SYMBOL(__memset64)
#endif
_GLOBAL_KASAN(memset)
_GLOBAL(memset)
neg r0,r3
rlwimi r4,r4,8,16,23
andi. r0,r0,7 /* # bytes to be 8-byte aligned */
@ -95,9 +94,8 @@ _GLOBAL_KASAN(memset)
stb r4,0(r6)
blr
EXPORT_SYMBOL(memset)
EXPORT_SYMBOL_KASAN(memset)
_GLOBAL_TOC_KASAN(memmove)
_GLOBAL_TOC(memmove)
cmplw 0,r3,r4
bgt backwards_memcpy
b memcpy
@ -139,4 +137,3 @@ _GLOBAL(backwards_memcpy)
mtctr r7
b 1b
EXPORT_SYMBOL(memmove)
EXPORT_SYMBOL_KASAN(memmove)

View File

@ -7,7 +7,6 @@
#include <asm/ppc_asm.h>
#include <asm/asm-compat.h>
#include <asm/feature-fixups.h>
#include <asm/kasan.h>
#ifndef SELFTEST_CASE
/* For big-endian, 0 == most CPUs, 1 == POWER6, 2 == Cell */
@ -15,7 +14,7 @@
#endif
.align 7
_GLOBAL_TOC_KASAN(memcpy)
_GLOBAL_TOC(memcpy)
BEGIN_FTR_SECTION
#ifdef __LITTLE_ENDIAN__
cmpdi cr7,r5,0
@ -227,4 +226,3 @@ END_FTR_SECTION_IFCLR(CPU_FTR_UNALIGNED_LD_STD)
blr
#endif
EXPORT_SYMBOL(memcpy)
EXPORT_SYMBOL_KASAN(memcpy)