Skip to content

Commit 156c75f

Browse files
author
Alexei Starovoitov
committed
Merge branch 'fix-ftrace-for-livepatch-bpf-fexit-programs'
Song Liu says: ==================== Fix ftrace for livepatch + BPF fexit programs livepatch and BPF trampoline are two special users of ftrace. livepatch uses ftrace with IPMODIFY flag and BPF trampoline uses ftrace direct functions. When livepatch and BPF trampoline with fexit programs attach to the same kernel function, BPF trampoline needs to call into the patched version of the kernel function. 1/3 and 2/3 of this patchset fix two issues with livepatch + fexit cases, one in the register_ftrace_direct path, the other in the modify_ftrace_direct path. 3/3 adds selftests for both cases. Acked-by: Steven Rostedt (Google) <rostedt@goodmis.org> --- v4: https://patch.msgid.link/20251027175023.1521602-1-song@kernel.org Changes v3 => v4: 1. Add helper reset_direct. (Steven) 2. Add Reviewed-by from Jiri. 3. Fix minor typo in comments. v3: https://lore.kernel.org/bpf/20251026205445.1639632-1-song@kernel.org/ Changes v2 => v3: 1. Incorporate feedback by AI, which also fixes build error reported by Steven and kernel test robot. v2: https://lore.kernel.org/bpf/20251024182901.3247573-1-song@kernel.org/ Changes v1 => v2: 1. Target bpf tree. (Alexei) 2. Bring back the FTRACE_WARN_ON in __ftrace_hash_update_ipmodify for valid code paths. (Steven) 3. Update selftests with cleaner way to find livepatch-sample.ko. (offlline discussion with Ihor) v1: https://lore.kernel.org/bpf/20251024071257.3956031-1-song@kernel.org/ ==================== Signed-off-by: Alexei Starovoitov <ast@kernel.org>
2 parents 6146a0f + 62d2d0a commit 156c75f

File tree

5 files changed

+185
-20
lines changed

5 files changed

+185
-20
lines changed

kernel/bpf/trampoline.c

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -479,11 +479,6 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mut
479479
* BPF_TRAMP_F_SHARE_IPMODIFY is set, we can generate the
480480
* trampoline again, and retry register.
481481
*/
482-
/* reset fops->func and fops->trampoline for re-register */
483-
tr->fops->func = NULL;
484-
tr->fops->trampoline = 0;
485-
486-
/* free im memory and reallocate later */
487482
bpf_tramp_image_free(im);
488483
goto again;
489484
}

kernel/trace/ftrace.c

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1971,7 +1971,8 @@ static void ftrace_hash_rec_enable_modify(struct ftrace_ops *ops)
19711971
*/
19721972
static int __ftrace_hash_update_ipmodify(struct ftrace_ops *ops,
19731973
struct ftrace_hash *old_hash,
1974-
struct ftrace_hash *new_hash)
1974+
struct ftrace_hash *new_hash,
1975+
bool update_target)
19751976
{
19761977
struct ftrace_page *pg;
19771978
struct dyn_ftrace *rec, *end = NULL;
@@ -2006,10 +2007,13 @@ static int __ftrace_hash_update_ipmodify(struct ftrace_ops *ops,
20062007
if (rec->flags & FTRACE_FL_DISABLED)
20072008
continue;
20082009

2009-
/* We need to update only differences of filter_hash */
2010+
/*
2011+
* Unless we are updating the target of a direct function,
2012+
* we only need to update differences of filter_hash
2013+
*/
20102014
in_old = !!ftrace_lookup_ip(old_hash, rec->ip);
20112015
in_new = !!ftrace_lookup_ip(new_hash, rec->ip);
2012-
if (in_old == in_new)
2016+
if (!update_target && (in_old == in_new))
20132017
continue;
20142018

20152019
if (in_new) {
@@ -2020,7 +2024,16 @@ static int __ftrace_hash_update_ipmodify(struct ftrace_ops *ops,
20202024
if (is_ipmodify)
20212025
goto rollback;
20222026

2023-
FTRACE_WARN_ON(rec->flags & FTRACE_FL_DIRECT);
2027+
/*
2028+
* If this is called by __modify_ftrace_direct()
2029+
* then it is only changing where the direct
2030+
* pointer is jumping to, and the record already
2031+
* points to a direct trampoline. If it isn't,
2032+
* then it is a bug to update ipmodify on a direct
2033+
* caller.
2034+
*/
2035+
FTRACE_WARN_ON(!update_target &&
2036+
(rec->flags & FTRACE_FL_DIRECT));
20242037

20252038
/*
20262039
* Another ops with IPMODIFY is already
@@ -2076,7 +2089,7 @@ static int ftrace_hash_ipmodify_enable(struct ftrace_ops *ops)
20762089
if (ftrace_hash_empty(hash))
20772090
hash = NULL;
20782091

2079-
return __ftrace_hash_update_ipmodify(ops, EMPTY_HASH, hash);
2092+
return __ftrace_hash_update_ipmodify(ops, EMPTY_HASH, hash, false);
20802093
}
20812094

20822095
/* Disabling always succeeds */
@@ -2087,7 +2100,7 @@ static void ftrace_hash_ipmodify_disable(struct ftrace_ops *ops)
20872100
if (ftrace_hash_empty(hash))
20882101
hash = NULL;
20892102

2090-
__ftrace_hash_update_ipmodify(ops, hash, EMPTY_HASH);
2103+
__ftrace_hash_update_ipmodify(ops, hash, EMPTY_HASH, false);
20912104
}
20922105

20932106
static int ftrace_hash_ipmodify_update(struct ftrace_ops *ops,
@@ -2101,7 +2114,7 @@ static int ftrace_hash_ipmodify_update(struct ftrace_ops *ops,
21012114
if (ftrace_hash_empty(new_hash))
21022115
new_hash = NULL;
21032116

2104-
return __ftrace_hash_update_ipmodify(ops, old_hash, new_hash);
2117+
return __ftrace_hash_update_ipmodify(ops, old_hash, new_hash, false);
21052118
}
21062119

21072120
static void print_ip_ins(const char *fmt, const unsigned char *p)
@@ -5953,6 +5966,17 @@ static void register_ftrace_direct_cb(struct rcu_head *rhp)
59535966
free_ftrace_hash(fhp);
59545967
}
59555968

5969+
static void reset_direct(struct ftrace_ops *ops, unsigned long addr)
5970+
{
5971+
struct ftrace_hash *hash = ops->func_hash->filter_hash;
5972+
5973+
remove_direct_functions_hash(hash, addr);
5974+
5975+
/* cleanup for possible another register call */
5976+
ops->func = NULL;
5977+
ops->trampoline = 0;
5978+
}
5979+
59565980
/**
59575981
* register_ftrace_direct - Call a custom trampoline directly
59585982
* for multiple functions registered in @ops
@@ -6048,6 +6072,8 @@ int register_ftrace_direct(struct ftrace_ops *ops, unsigned long addr)
60486072
ops->direct_call = addr;
60496073

60506074
err = register_ftrace_function_nolock(ops);
6075+
if (err)
6076+
reset_direct(ops, addr);
60516077

60526078
out_unlock:
60536079
mutex_unlock(&direct_mutex);
@@ -6080,7 +6106,6 @@ EXPORT_SYMBOL_GPL(register_ftrace_direct);
60806106
int unregister_ftrace_direct(struct ftrace_ops *ops, unsigned long addr,
60816107
bool free_filters)
60826108
{
6083-
struct ftrace_hash *hash = ops->func_hash->filter_hash;
60846109
int err;
60856110

60866111
if (check_direct_multi(ops))
@@ -6090,13 +6115,9 @@ int unregister_ftrace_direct(struct ftrace_ops *ops, unsigned long addr,
60906115

60916116
mutex_lock(&direct_mutex);
60926117
err = unregister_ftrace_function(ops);
6093-
remove_direct_functions_hash(hash, addr);
6118+
reset_direct(ops, addr);
60946119
mutex_unlock(&direct_mutex);
60956120

6096-
/* cleanup for possible another register call */
6097-
ops->func = NULL;
6098-
ops->trampoline = 0;
6099-
61006121
if (free_filters)
61016122
ftrace_free_filter(ops);
61026123
return err;
@@ -6106,7 +6127,7 @@ EXPORT_SYMBOL_GPL(unregister_ftrace_direct);
61066127
static int
61076128
__modify_ftrace_direct(struct ftrace_ops *ops, unsigned long addr)
61086129
{
6109-
struct ftrace_hash *hash;
6130+
struct ftrace_hash *hash = ops->func_hash->filter_hash;
61106131
struct ftrace_func_entry *entry, *iter;
61116132
static struct ftrace_ops tmp_ops = {
61126133
.func = ftrace_stub,
@@ -6126,13 +6147,21 @@ __modify_ftrace_direct(struct ftrace_ops *ops, unsigned long addr)
61266147
if (err)
61276148
return err;
61286149

6150+
/*
6151+
* Call __ftrace_hash_update_ipmodify() here, so that we can call
6152+
* ops->ops_func for the ops. This is needed because the above
6153+
* register_ftrace_function_nolock() worked on tmp_ops.
6154+
*/
6155+
err = __ftrace_hash_update_ipmodify(ops, hash, hash, true);
6156+
if (err)
6157+
goto out;
6158+
61296159
/*
61306160
* Now the ftrace_ops_list_func() is called to do the direct callers.
61316161
* We can safely change the direct functions attached to each entry.
61326162
*/
61336163
mutex_lock(&ftrace_lock);
61346164

6135-
hash = ops->func_hash->filter_hash;
61366165
size = 1 << hash->size_bits;
61376166
for (i = 0; i < size; i++) {
61386167
hlist_for_each_entry(iter, &hash->buckets[i], hlist) {
@@ -6147,6 +6176,7 @@ __modify_ftrace_direct(struct ftrace_ops *ops, unsigned long addr)
61476176

61486177
mutex_unlock(&ftrace_lock);
61496178

6179+
out:
61506180
/* Removing the tmp_ops will add the updated direct callers to the functions */
61516181
unregister_ftrace_function(&tmp_ops);
61526182

tools/testing/selftests/bpf/config

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ CONFIG_IPV6_SIT=y
5050
CONFIG_IPV6_TUNNEL=y
5151
CONFIG_KEYS=y
5252
CONFIG_LIRC=y
53+
CONFIG_LIVEPATCH=y
5354
CONFIG_LWTUNNEL=y
5455
CONFIG_MODULE_SIG=y
5556
CONFIG_MODULE_SRCVERSION_ALL=y
@@ -111,6 +112,8 @@ CONFIG_IP6_NF_FILTER=y
111112
CONFIG_NF_NAT=y
112113
CONFIG_PACKET=y
113114
CONFIG_RC_CORE=y
115+
CONFIG_SAMPLES=y
116+
CONFIG_SAMPLE_LIVEPATCH=m
114117
CONFIG_SECURITY=y
115118
CONFIG_SECURITYFS=y
116119
CONFIG_SYN_COOKIES=y
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
3+
4+
#include <test_progs.h>
5+
#include "testing_helpers.h"
6+
#include "livepatch_trampoline.skel.h"
7+
8+
static int load_livepatch(void)
9+
{
10+
char path[4096];
11+
12+
/* CI will set KBUILD_OUTPUT */
13+
snprintf(path, sizeof(path), "%s/samples/livepatch/livepatch-sample.ko",
14+
getenv("KBUILD_OUTPUT") ? : "../../../..");
15+
16+
return load_module(path, env_verbosity > VERBOSE_NONE);
17+
}
18+
19+
static void unload_livepatch(void)
20+
{
21+
/* Disable the livepatch before unloading the module */
22+
system("echo 0 > /sys/kernel/livepatch/livepatch_sample/enabled");
23+
24+
unload_module("livepatch_sample", env_verbosity > VERBOSE_NONE);
25+
}
26+
27+
static void read_proc_cmdline(void)
28+
{
29+
char buf[4096];
30+
int fd, ret;
31+
32+
fd = open("/proc/cmdline", O_RDONLY);
33+
if (!ASSERT_OK_FD(fd, "open /proc/cmdline"))
34+
return;
35+
36+
ret = read(fd, buf, sizeof(buf));
37+
if (!ASSERT_GT(ret, 0, "read /proc/cmdline"))
38+
goto out;
39+
40+
ASSERT_OK(strncmp(buf, "this has been live patched", 26), "strncmp");
41+
42+
out:
43+
close(fd);
44+
}
45+
46+
static void __test_livepatch_trampoline(bool fexit_first)
47+
{
48+
struct livepatch_trampoline *skel = NULL;
49+
int err;
50+
51+
skel = livepatch_trampoline__open_and_load();
52+
if (!ASSERT_OK_PTR(skel, "skel_open_and_load"))
53+
goto out;
54+
55+
skel->bss->my_pid = getpid();
56+
57+
if (!fexit_first) {
58+
/* fentry program is loaded first by default */
59+
err = livepatch_trampoline__attach(skel);
60+
if (!ASSERT_OK(err, "skel_attach"))
61+
goto out;
62+
} else {
63+
/* Manually load fexit program first. */
64+
skel->links.fexit_cmdline = bpf_program__attach(skel->progs.fexit_cmdline);
65+
if (!ASSERT_OK_PTR(skel->links.fexit_cmdline, "attach_fexit"))
66+
goto out;
67+
68+
skel->links.fentry_cmdline = bpf_program__attach(skel->progs.fentry_cmdline);
69+
if (!ASSERT_OK_PTR(skel->links.fentry_cmdline, "attach_fentry"))
70+
goto out;
71+
}
72+
73+
read_proc_cmdline();
74+
75+
ASSERT_EQ(skel->bss->fentry_hit, 1, "fentry_hit");
76+
ASSERT_EQ(skel->bss->fexit_hit, 1, "fexit_hit");
77+
out:
78+
livepatch_trampoline__destroy(skel);
79+
}
80+
81+
void test_livepatch_trampoline(void)
82+
{
83+
int retry_cnt = 0;
84+
85+
retry:
86+
if (load_livepatch()) {
87+
if (retry_cnt) {
88+
ASSERT_OK(1, "load_livepatch");
89+
goto out;
90+
}
91+
/*
92+
* Something else (previous run of the same test?) loaded
93+
* the KLP module. Unload the KLP module and retry.
94+
*/
95+
unload_livepatch();
96+
retry_cnt++;
97+
goto retry;
98+
}
99+
100+
if (test__start_subtest("fentry_first"))
101+
__test_livepatch_trampoline(false);
102+
103+
if (test__start_subtest("fexit_first"))
104+
__test_livepatch_trampoline(true);
105+
out:
106+
unload_livepatch();
107+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
3+
4+
#include <linux/bpf.h>
5+
#include <bpf/bpf_helpers.h>
6+
#include <bpf/bpf_tracing.h>
7+
8+
int fentry_hit;
9+
int fexit_hit;
10+
int my_pid;
11+
12+
SEC("fentry/cmdline_proc_show")
13+
int BPF_PROG(fentry_cmdline)
14+
{
15+
if (my_pid != (bpf_get_current_pid_tgid() >> 32))
16+
return 0;
17+
18+
fentry_hit = 1;
19+
return 0;
20+
}
21+
22+
SEC("fexit/cmdline_proc_show")
23+
int BPF_PROG(fexit_cmdline)
24+
{
25+
if (my_pid != (bpf_get_current_pid_tgid() >> 32))
26+
return 0;
27+
28+
fexit_hit = 1;
29+
return 0;
30+
}

0 commit comments

Comments
 (0)