Skip to content

Commit b39181f

Browse files
committed
ftrace: Add FTRACE_MCOUNT_MAX_OFFSET to avoid adding weak function
If an unused weak function was traced, it's call to fentry will still exist, which gets added into the __mcount_loc table. Ftrace will use kallsyms to retrieve the name for each location in __mcount_loc to display it in the available_filter_functions and used to enable functions via the name matching in set_ftrace_filter/notrace. Enabling these functions do nothing but enable an unused call to ftrace_caller. If a traced weak function is overridden, the symbol of the function would be used for it, which will either created duplicate names, or if the previous function was not traced, it would be incorrectly be listed in available_filter_functions as a function that can be traced. This became an issue with BPF[1] as there are tooling that enables the direct callers via ftrace but then checks to see if the functions were actually enabled. The case of one function that was marked notrace, but was followed by an unused weak function that was traced. The unused function's call to fentry was added to the __mcount_loc section, and kallsyms retrieved the untraced function's symbol as the weak function was overridden. Since the untraced function would not get traced, the BPF check would detect this and fail. The real fix would be to fix kallsyms to not show addresses of weak functions as the function before it. But that would require adding code in the build to add function size to kallsyms so that it can know when the function ends instead of just using the start of the next known symbol. In the mean time, this is a work around. Add a FTRACE_MCOUNT_MAX_OFFSET macro that if defined, ftrace will ignore any function that has its call to fentry/mcount that has an offset from the symbol that is greater than FTRACE_MCOUNT_MAX_OFFSET. If CONFIG_HAVE_FENTRY is defined for x86, define FTRACE_MCOUNT_MAX_OFFSET to zero (unless IBT is enabled), which will have ftrace ignore all locations that are not at the start of the function (or one after the ENDBR instruction). A worker thread is added at boot up to scan all the ftrace record entries, and will mark any that fail the FTRACE_MCOUNT_MAX_OFFSET test as disabled. They will still appear in the available_filter_functions file as: __ftrace_invalid_address___<invalid-offset> (showing the offset that caused it to be invalid). This is required for tools that use libtracefs (like trace-cmd does) that scan the available_filter_functions and enable set_ftrace_filter and set_ftrace_notrace using indexes of the function listed in the file (this is a speedup, as enabling thousands of files via names is an O(n^2) operation and can take minutes to complete, where the indexing takes less than a second). The invalid functions cannot be removed from available_filter_functions as the names there correspond to the ftrace records in the array that manages them (and the indexing depends on this). [1] https://lore.kernel.org/all/20220412094923.0abe90955e5db486b7bca279@kernel.org/ Link: https://lkml.kernel.org/r/20220526141912.794c2786@gandalf.local.home Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
1 parent 8d4a21b commit b39181f

2 files changed

Lines changed: 146 additions & 2 deletions

File tree

arch/x86/include/asm/ftrace.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@
99
# define MCOUNT_ADDR ((unsigned long)(__fentry__))
1010
#define MCOUNT_INSN_SIZE 5 /* sizeof mcount call */
1111

12+
/* Ignore unused weak functions which will have non zero offsets */
13+
#ifdef CONFIG_HAVE_FENTRY
14+
# include <asm/ibt.h>
15+
/* Add offset for endbr64 if IBT enabled */
16+
# define FTRACE_MCOUNT_MAX_OFFSET ENDBR_INSN_SIZE
17+
#endif
18+
1219
#ifdef CONFIG_DYNAMIC_FTRACE
1320
#define ARCH_SUPPORTS_FTRACE_OPS 1
1421
#endif

kernel/trace/ftrace.c

Lines changed: 139 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
#include "trace_output.h"
4646
#include "trace_stat.h"
4747

48+
#define FTRACE_INVALID_FUNCTION "__ftrace_invalid_address__"
49+
4850
#define FTRACE_WARN_ON(cond) \
4951
({ \
5052
int ___r = cond; \
@@ -3654,6 +3656,105 @@ static void add_trampoline_func(struct seq_file *m, struct ftrace_ops *ops,
36543656
seq_printf(m, " ->%pS", ptr);
36553657
}
36563658

3659+
#ifdef FTRACE_MCOUNT_MAX_OFFSET
3660+
/*
3661+
* Weak functions can still have an mcount/fentry that is saved in
3662+
* the __mcount_loc section. These can be detected by having a
3663+
* symbol offset of greater than FTRACE_MCOUNT_MAX_OFFSET, as the
3664+
* symbol found by kallsyms is not the function that the mcount/fentry
3665+
* is part of. The offset is much greater in these cases.
3666+
*
3667+
* Test the record to make sure that the ip points to a valid kallsyms
3668+
* and if not, mark it disabled.
3669+
*/
3670+
static int test_for_valid_rec(struct dyn_ftrace *rec)
3671+
{
3672+
char str[KSYM_SYMBOL_LEN];
3673+
unsigned long offset;
3674+
const char *ret;
3675+
3676+
ret = kallsyms_lookup(rec->ip, NULL, &offset, NULL, str);
3677+
3678+
/* Weak functions can cause invalid addresses */
3679+
if (!ret || offset > FTRACE_MCOUNT_MAX_OFFSET) {
3680+
rec->flags |= FTRACE_FL_DISABLED;
3681+
return 0;
3682+
}
3683+
return 1;
3684+
}
3685+
3686+
static struct workqueue_struct *ftrace_check_wq __initdata;
3687+
static struct work_struct ftrace_check_work __initdata;
3688+
3689+
/*
3690+
* Scan all the mcount/fentry entries to make sure they are valid.
3691+
*/
3692+
static __init void ftrace_check_work_func(struct work_struct *work)
3693+
{
3694+
struct ftrace_page *pg;
3695+
struct dyn_ftrace *rec;
3696+
3697+
mutex_lock(&ftrace_lock);
3698+
do_for_each_ftrace_rec(pg, rec) {
3699+
test_for_valid_rec(rec);
3700+
} while_for_each_ftrace_rec();
3701+
mutex_unlock(&ftrace_lock);
3702+
}
3703+
3704+
static int __init ftrace_check_for_weak_functions(void)
3705+
{
3706+
INIT_WORK(&ftrace_check_work, ftrace_check_work_func);
3707+
3708+
ftrace_check_wq = alloc_workqueue("ftrace_check_wq", WQ_UNBOUND, 0);
3709+
3710+
queue_work(ftrace_check_wq, &ftrace_check_work);
3711+
return 0;
3712+
}
3713+
3714+
static int __init ftrace_check_sync(void)
3715+
{
3716+
/* Make sure the ftrace_check updates are finished */
3717+
if (ftrace_check_wq)
3718+
destroy_workqueue(ftrace_check_wq);
3719+
return 0;
3720+
}
3721+
3722+
late_initcall_sync(ftrace_check_sync);
3723+
subsys_initcall(ftrace_check_for_weak_functions);
3724+
3725+
static int print_rec(struct seq_file *m, unsigned long ip)
3726+
{
3727+
unsigned long offset;
3728+
char str[KSYM_SYMBOL_LEN];
3729+
char *modname;
3730+
const char *ret;
3731+
3732+
ret = kallsyms_lookup(ip, NULL, &offset, &modname, str);
3733+
/* Weak functions can cause invalid addresses */
3734+
if (!ret || offset > FTRACE_MCOUNT_MAX_OFFSET) {
3735+
snprintf(str, KSYM_SYMBOL_LEN, "%s_%ld",
3736+
FTRACE_INVALID_FUNCTION, offset);
3737+
ret = NULL;
3738+
}
3739+
3740+
seq_puts(m, str);
3741+
if (modname)
3742+
seq_printf(m, " [%s]", modname);
3743+
return ret == NULL ? -1 : 0;
3744+
}
3745+
#else
3746+
static inline int test_for_valid_rec(struct dyn_ftrace *rec)
3747+
{
3748+
return 1;
3749+
}
3750+
3751+
static inline int print_rec(struct seq_file *m, unsigned long ip)
3752+
{
3753+
seq_printf(m, "%ps", (void *)ip);
3754+
return 0;
3755+
}
3756+
#endif
3757+
36573758
static int t_show(struct seq_file *m, void *v)
36583759
{
36593760
struct ftrace_iterator *iter = m->private;
@@ -3678,7 +3779,13 @@ static int t_show(struct seq_file *m, void *v)
36783779
if (!rec)
36793780
return 0;
36803781

3681-
seq_printf(m, "%ps", (void *)rec->ip);
3782+
if (print_rec(m, rec->ip)) {
3783+
/* This should only happen when a rec is disabled */
3784+
WARN_ON_ONCE(!(rec->flags & FTRACE_FL_DISABLED));
3785+
seq_putc(m, '\n');
3786+
return 0;
3787+
}
3788+
36823789
if (iter->flags & FTRACE_ITER_ENABLED) {
36833790
struct ftrace_ops *ops;
36843791

@@ -3996,14 +4103,37 @@ add_rec_by_index(struct ftrace_hash *hash, struct ftrace_glob *func_g,
39964103
return 0;
39974104
}
39984105

4106+
#ifdef FTRACE_MCOUNT_MAX_OFFSET
4107+
static int lookup_ip(unsigned long ip, char **modname, char *str)
4108+
{
4109+
unsigned long offset;
4110+
4111+
kallsyms_lookup(ip, NULL, &offset, modname, str);
4112+
if (offset > FTRACE_MCOUNT_MAX_OFFSET)
4113+
return -1;
4114+
return 0;
4115+
}
4116+
#else
4117+
static int lookup_ip(unsigned long ip, char **modname, char *str)
4118+
{
4119+
kallsyms_lookup(ip, NULL, NULL, modname, str);
4120+
return 0;
4121+
}
4122+
#endif
4123+
39994124
static int
40004125
ftrace_match_record(struct dyn_ftrace *rec, struct ftrace_glob *func_g,
40014126
struct ftrace_glob *mod_g, int exclude_mod)
40024127
{
40034128
char str[KSYM_SYMBOL_LEN];
40044129
char *modname;
40054130

4006-
kallsyms_lookup(rec->ip, NULL, NULL, &modname, str);
4131+
if (lookup_ip(rec->ip, &modname, str)) {
4132+
/* This should only happen when a rec is disabled */
4133+
WARN_ON_ONCE(system_state == SYSTEM_RUNNING &&
4134+
!(rec->flags & FTRACE_FL_DISABLED));
4135+
return 0;
4136+
}
40074137

40084138
if (mod_g) {
40094139
int mod_matches = (modname) ? ftrace_match(modname, mod_g) : 0;
@@ -6819,6 +6949,13 @@ void ftrace_module_enable(struct module *mod)
68196949
!within_module_init(rec->ip, mod))
68206950
break;
68216951

6952+
/* Weak functions should still be ignored */
6953+
if (!test_for_valid_rec(rec)) {
6954+
/* Clear all other flags. Should not be enabled anyway */
6955+
rec->flags = FTRACE_FL_DISABLED;
6956+
continue;
6957+
}
6958+
68226959
cnt = 0;
68236960

68246961
/*

0 commit comments

Comments
 (0)