]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
MIPS: ftrace: Fix memory corruption when kernel is located beyond 32 bits
authorGregory CLEMENT <gregory.clement@bootlin.com>
Fri, 28 Nov 2025 08:30:06 +0000 (09:30 +0100)
committerThomas Bogendoerfer <tsbogend@alpha.franken.de>
Mon, 1 Dec 2025 09:07:25 +0000 (10:07 +0100)
Since commit e424054000878 ("MIPS: Tracing: Reduce the overhead of
dynamic Function Tracer"), the macro UASM_i_LA_mostly has been used,
and this macro can generate more than 2 instructions. At the same
time, the code in ftrace assumes that no more than 2 instructions can
be generated, which is why it stores them in an int[2] array. However,
as previously noted, the macro UASM_i_LA_mostly (and now UASM_i_LA)
causes a buffer overflow when _mcount is beyond 32 bits. This leads to
corruption of the variables located in the __read_mostly section.

This corruption was observed because the variable
__cpu_primary_thread_mask was corrupted, causing a hang very early
during boot.

This fix prevents the corruption by avoiding the generation of
instructions if they could exceed 2 instructions in
length. Fortunately, insn_la_mcount is only used if the instrumented
code is located outside the kernel code section, so dynamic ftrace can
still be used, albeit in a more limited scope. This is still
preferable to corrupting memory and/or crashing the kernel.

Signed-off-by: Gregory CLEMENT <gregory.clement@bootlin.com>
Signed-off-by: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
arch/mips/kernel/ftrace.c

index f39e85fd58fa994bfed2c122462b3b83a774c167..b15615b285690708f2581dc2dfc62d62c2fcea3f 100644 (file)
@@ -54,10 +54,20 @@ static inline void ftrace_dyn_arch_init_insns(void)
        u32 *buf;
        unsigned int v1;
 
-       /* la v1, _mcount */
-       v1 = 3;
-       buf = (u32 *)&insn_la_mcount[0];
-       UASM_i_LA(&buf, v1, MCOUNT_ADDR);
+       /* If we are not in compat space, the number of generated
+        * instructions will exceed the maximum expected limit of 2.
+        * To prevent buffer overflow, we avoid generating them.
+        * insn_la_mcount will not be used later in ftrace_make_call.
+        */
+       if (uasm_in_compat_space_p(MCOUNT_ADDR)) {
+               /* la v1, _mcount */
+               v1 = 3;
+               buf = (u32 *)&insn_la_mcount[0];
+               UASM_i_LA(&buf, v1, MCOUNT_ADDR);
+       } else {
+               pr_warn("ftrace: mcount address beyond 32 bits is not supported (%lX)\n",
+                       MCOUNT_ADDR);
+       }
 
        /* jal (ftrace_caller + 8), jump over the first two instruction */
        buf = (u32 *)&insn_jal_ftrace_caller;
@@ -189,6 +199,13 @@ int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
        unsigned int new;
        unsigned long ip = rec->ip;
 
+       /* When the code to patch does not belong to the kernel code
+        * space, we must use insn_la_mcount. However, if MCOUNT_ADDR
+        * is not in compat space, insn_la_mcount is not usable.
+        */
+       if (!core_kernel_text(ip) && !uasm_in_compat_space_p(MCOUNT_ADDR))
+               return -EFAULT;
+
        new = core_kernel_text(ip) ? insn_jal_ftrace_caller : insn_la_mcount[0];
 
 #ifdef CONFIG_64BIT