From nobody Wed May 8 18:18:50 2024 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 270FDC433F5 for ; Tue, 24 May 2022 00:18:29 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232085AbiEXAS1 (ORCPT ); Mon, 23 May 2022 20:18:27 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:33504 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232076AbiEXAR2 (ORCPT ); Mon, 23 May 2022 20:17:28 -0400 Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id B4CA792D12; Mon, 23 May 2022 17:17:05 -0700 (PDT) Received: from x64host.home (unknown [47.189.24.195]) by linux.microsoft.com (Postfix) with ESMTPSA id 8509520B5B4E; Mon, 23 May 2022 17:17:04 -0700 (PDT) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com 8509520B5B4E DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.microsoft.com; s=default; t=1653351425; bh=F+06QZixvxLg5mYB/7rdwOhFtinfWWMMUPEPRY4OtFI=; h=From:To:Subject:Date:In-Reply-To:References:From; b=fNUshMRvBVgso8WBJO4GkUQQK0Q8bi4QV+zVHOgHgrHmj0j+0jo9zP6py6SVb8xip fjz8QwDwctqwlJYT+ENWJT3lZZrgUpArvBvHx2po1pDhGjUVvmrLR3bfnkF2xE097s cq2Ds76QQ4c4IGWT4Sqi2kkTj0xKg50unsT0I/0Q= From: madvenka@linux.microsoft.com To: jpoimboe@redhat.com, peterz@infradead.org, chenzhongjin@huawei.com, mark.rutland@arm.com, broonie@kernel.org, nobuta.keiya@fujitsu.com, sjitindarsingh@gmail.com, catalin.marinas@arm.com, will@kernel.org, jamorris@linux.microsoft.com, linux-arm-kernel@lists.infradead.org, live-patching@vger.kernel.org, linux-kernel@vger.kernel.org, madvenka@linux.microsoft.com Subject: [RFC PATCH v2 18/20] arm64: unwinder: Add a reliability check in the unwinder based on ORC Date: Mon, 23 May 2022 19:16:35 -0500 Message-Id: <20220524001637.1707472-19-madvenka@linux.microsoft.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220524001637.1707472-1-madvenka@linux.microsoft.com> References: <20220524001637.1707472-1-madvenka@linux.microsoft.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" From: "Madhavan T. Venkataraman" Introduce a reliability flag in struct stackframe. This will be set to false if the PC does not have a valid ORC or if the frame pointer computed from the ORC does not match the actual frame pointer. Now that the unwinder can validate the frame pointer, introduce arch_stack_walk_reliable(). Signed-off-by: Madhavan T. Venkataraman --- arch/arm64/include/asm/stacktrace.h | 9 ++ arch/arm64/kernel/ftrace.c | 16 +++ arch/arm64/kernel/stacktrace.c | 153 ++++++++++++++++++++++++++++ include/linux/ftrace.h | 4 + 4 files changed, 182 insertions(+) diff --git a/arch/arm64/include/asm/stacktrace.h b/arch/arm64/include/asm/s= tacktrace.h index e77cdef9ca29..f5d9bbed53e6 100644 --- a/arch/arm64/include/asm/stacktrace.h +++ b/arch/arm64/include/asm/stacktrace.h @@ -44,6 +44,7 @@ struct stack_info { * @prev_fp: The fp that pointed to this frame record, or a synthetic = value * of 0. This is used to ensure that within a stack, each * subsequent frame record is at an increasing address. + * @prev_pc: The pc in the previous frame. * @prev_type: The type of stack this frame record was on, or a synthetic * value of STACK_TYPE_UNKNOWN. This is used to detect a * transition from one stack to another. @@ -51,16 +52,24 @@ struct stack_info { * @kr_cur: When KRETPROBES is selected, holds the kretprobe instance * associated with the most recently encountered replacement= lr * value. + * + * @cfa: The sp value at the call site of the current function. + * @unwind_type The previous frame's unwind type. + * @reliable: Stack trace is reliable. */ struct stackframe { unsigned long fp; unsigned long pc; DECLARE_BITMAP(stacks_done, __NR_STACK_TYPES); unsigned long prev_fp; + unsigned long prev_pc; enum stack_type prev_type; #ifdef CONFIG_KRETPROBES struct llist_node *kr_cur; #endif + unsigned long cfa; + int unwind_type; + bool reliable; }; =20 extern void dump_backtrace(struct pt_regs *regs, struct task_struct *tsk, diff --git a/arch/arm64/kernel/ftrace.c b/arch/arm64/kernel/ftrace.c index 4506c4a90ac1..ec9a00d714e5 100644 --- a/arch/arm64/kernel/ftrace.c +++ b/arch/arm64/kernel/ftrace.c @@ -299,3 +299,19 @@ int ftrace_disable_ftrace_graph_caller(void) } #endif /* CONFIG_DYNAMIC_FTRACE */ #endif /* CONFIG_FUNCTION_GRAPH_TRACER */ + +#ifdef CONFIG_DYNAMIC_FTRACE_WITH_REGS + +bool is_ftrace_entry(unsigned long pc) +{ + if (pc =3D=3D (unsigned long)&ftrace_call_entry) + return true; + +#ifdef CONFIG_FUNCTION_GRAPH_TRACER + if (pc =3D=3D (unsigned long)&ftrace_graph_caller_entry) + return true; +#endif + return false; +} + +#endif diff --git a/arch/arm64/kernel/stacktrace.c b/arch/arm64/kernel/stacktrace.c index e4103e085681..31184d64edb6 100644 --- a/arch/arm64/kernel/stacktrace.c +++ b/arch/arm64/kernel/stacktrace.c @@ -5,6 +5,8 @@ * Copyright (C) 2012 ARM Ltd. */ #include +#include +#include #include #include #include @@ -18,6 +20,120 @@ #include #include =20 +#ifdef CONFIG_FRAME_POINTER_VALIDATION + +static void unwind_check_frame(struct stackframe *frame) +{ + unsigned long pc, fp; + struct orc_entry *orc; + bool adjust_pc =3D false; + + /* + * If a previous frame was unreliable, the CFA cannot be reliably + * computed anymore. + */ + if (!frame->reliable) + return; + + pc =3D frame->pc; +#ifdef CONFIG_DYNAMIC_FTRACE_WITH_REGS + if (is_ftrace_entry(frame->prev_pc)) + pc =3D (unsigned long)&ftrace_callsite; +#endif + + /* Don't let modules unload while we're reading their ORC data. */ + preempt_disable(); + + orc =3D orc_find(pc); + if (!orc || (!orc->fp_offset && orc->type =3D=3D UNWIND_HINT_TYPE_CALL)) { + /* + * If the final instruction in a function happens to be a call + * instruction, the return address would fall outside of the + * function. That could be the case here. This can happen, for + * instance, if the called function is a "noreturn" function. + * The compiler can optimize away the instructions after the + * call. So, adjust the PC so it falls inside the function and + * retry. + * + * We only do this if the current and the previous frames + * are call frames and not hint frames. + */ + if (frame->unwind_type =3D=3D UNWIND_HINT_TYPE_CALL) { + pc -=3D 4; + adjust_pc =3D true; + orc =3D orc_find(pc); + } + } + if (!orc) { + frame->reliable =3D false; + goto out; + } + frame->unwind_type =3D orc->type; + + if (!frame->cfa) { + /* Set up the initial CFA and return. */ + frame->cfa =3D frame->fp - orc->fp_offset; + goto out; + } + + /* Compute the next CFA and FP. */ + switch (orc->type) { + case UNWIND_HINT_TYPE_CALL: + /* Normal call */ + frame->cfa +=3D orc->sp_offset; + fp =3D frame->cfa + orc->fp_offset; + break; + + case UNWIND_HINT_TYPE_REGS: + /* + * pt_regs hint: The frame pointer points to either the + * synthetic frame within pt_regs or to the place where + * x29 and x30 are saved in the register save area in + * pt_regs. + */ + frame->cfa +=3D orc->sp_offset; + fp =3D frame->cfa + offsetof(struct pt_regs, stackframe) - + sizeof(struct pt_regs); + if (frame->fp !=3D fp) { + fp =3D frame->cfa + offsetof(struct pt_regs, regs[29]) - + sizeof(struct pt_regs); + } + break; + + case UNWIND_HINT_TYPE_FTRACE: + /* ftrace callsite hint */ + frame->cfa +=3D orc->sp_offset; + fp =3D frame->cfa - orc->sp_offset; + break; + + case UNWIND_HINT_TYPE_IRQ_STACK: + /* Hint to unwind from the IRQ stack to the task stack. */ + frame->cfa =3D frame->fp + orc->sp_offset; + fp =3D frame->fp; + break; + + default: + fp =3D 0; + break; + } + + /* Validate the actual FP with the computed one. */ + if (frame->fp !=3D fp) + frame->reliable =3D false; +out: + if (frame->reliable && adjust_pc) + frame->pc =3D pc; + preempt_enable(); +} + +#else /* !CONFIG_FRAME_POINTER_VALIDATION */ + +static void unwind_check_frame(struct stackframe *frame) +{ +} + +#endif /* CONFIG_FRAME_POINTER_VALIDATION */ + /* * AArch64 PCS assigns the frame pointer to x29. * @@ -53,7 +169,13 @@ static notrace void start_backtrace(struct stackframe *= frame, unsigned long fp, */ bitmap_zero(frame->stacks_done, __NR_STACK_TYPES); frame->prev_fp =3D 0; + frame->prev_pc =3D 0; frame->prev_type =3D STACK_TYPE_UNKNOWN; + + frame->reliable =3D true; + frame->cfa =3D 0; + frame->unwind_type =3D UNWIND_HINT_TYPE_CALL; + unwind_check_frame(frame); } NOKPROBE_SYMBOL(start_backtrace); =20 @@ -110,6 +232,7 @@ static int notrace unwind_frame(struct task_struct *tsk, * Record this frame record's values and location. The prev_fp and * prev_type are only meaningful to the next unwind_frame() invocation. */ + frame->prev_pc =3D frame->pc; frame->fp =3D READ_ONCE_NOCHECK(*(unsigned long *)(fp)); frame->pc =3D READ_ONCE_NOCHECK(*(unsigned long *)(fp + 8)); frame->prev_fp =3D fp; @@ -139,6 +262,10 @@ static int notrace unwind_frame(struct task_struct *ts= k, frame->pc =3D kretprobe_find_ret_addr(tsk, (void *)frame->fp, &frame->kr= _cur); #endif =20 + /* If it is the final frame, no need to check reliability. */ + if (frame->fp !=3D (unsigned long)task_pt_regs(tsk)->stackframe) + unwind_check_frame(frame); + return 0; } NOKPROBE_SYMBOL(unwind_frame); @@ -210,3 +337,29 @@ noinline notrace void arch_stack_walk(stack_trace_cons= ume_fn consume_entry, =20 walk_stackframe(task, &frame, consume_entry, cookie); } + +noinline int arch_stack_walk_reliable(stack_trace_consume_fn consume_entry, + void *cookie, struct task_struct *task) +{ + struct stackframe frame; + int ret =3D 0; + + if (task =3D=3D current) { + start_backtrace(&frame, + (unsigned long)__builtin_frame_address(1), + (unsigned long)__builtin_return_address(0)); + } else { + start_backtrace(&frame, thread_saved_fp(task), + thread_saved_pc(task)); + } + + while (!ret) { + if (!frame.reliable) + return -EINVAL; + if (!consume_entry(cookie, frame.pc)) + return -EINVAL; + ret =3D unwind_frame(task, &frame); + } + + return ret =3D=3D -ENOENT ? 0 : -EINVAL; +} diff --git a/include/linux/ftrace.h b/include/linux/ftrace.h index 4816b7e11047..2324f0b25674 100644 --- a/include/linux/ftrace.h +++ b/include/linux/ftrace.h @@ -616,6 +616,10 @@ extern int ftrace_update_ftrace_func(ftrace_func_t fun= c); extern void ftrace_caller(void); extern void ftrace_regs_caller(void); extern void ftrace_call(void); +extern void ftrace_call_entry(void); +extern void ftrace_graph_caller_entry(void); +extern void ftrace_callsite(void); +extern bool is_ftrace_entry(unsigned long pc); extern void ftrace_regs_call(void); extern void mcount_call(void); =20 --=20 2.25.1