Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions kernel/bpf/trampoline.c
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,13 @@ static int register_fentry(struct bpf_trampoline *tr, void *new_addr)

if (tr->func.ftrace_managed) {
ftrace_set_filter_ip(tr->fops, (unsigned long)ip, 0, 1);
/*
* Clearing fops->trampoline and fops->NULL is
* needed by the "goto again" case in
* bpf_trampoline_update().
*/
tr->fops->trampoline = 0;
tr->fops->func = NULL;
ret = register_ftrace_direct(tr->fops, (long)new_addr);
} else {
ret = bpf_arch_text_poke(ip, BPF_MOD_CALL, NULL, new_addr);
Expand Down Expand Up @@ -479,11 +486,6 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mut
* BPF_TRAMP_F_SHARE_IPMODIFY is set, we can generate the
* trampoline again, and retry register.
*/
/* reset fops->func and fops->trampoline for re-register */
tr->fops->func = NULL;
tr->fops->trampoline = 0;

/* free im memory and reallocate later */
bpf_tramp_image_free(im);
goto again;
}
Expand Down
42 changes: 33 additions & 9 deletions kernel/trace/ftrace.c
Original file line number Diff line number Diff line change
Expand Up @@ -1971,7 +1971,8 @@ static void ftrace_hash_rec_enable_modify(struct ftrace_ops *ops)
*/
static int __ftrace_hash_update_ipmodify(struct ftrace_ops *ops,
struct ftrace_hash *old_hash,
struct ftrace_hash *new_hash)
struct ftrace_hash *new_hash,
bool update_target)
{
struct ftrace_page *pg;
struct dyn_ftrace *rec, *end = NULL;
Expand Down Expand Up @@ -2006,10 +2007,13 @@ static int __ftrace_hash_update_ipmodify(struct ftrace_ops *ops,
if (rec->flags & FTRACE_FL_DISABLED)
continue;

/* We need to update only differences of filter_hash */
/*
* Unless we are updating the target of a direct function,
* we only need to update differences of filter_hash
*/
in_old = !!ftrace_lookup_ip(old_hash, rec->ip);
in_new = !!ftrace_lookup_ip(new_hash, rec->ip);
if (in_old == in_new)
if (!update_target && (in_old == in_new))
continue;

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

FTRACE_WARN_ON(rec->flags & FTRACE_FL_DIRECT);
/*
* If this is called by __modify_ftrace_direct()
* then it is only chaning where the direct
* pointer is jumping to, and the record already
* points to a direct trampoline. If it isn't
* then it is a bug to update ipmodify on a direct
* caller.
*/
FTRACE_WARN_ON(!update_target &&
(rec->flags & FTRACE_FL_DIRECT));

/*
* Another ops with IPMODIFY is already
Expand Down Expand Up @@ -2076,7 +2089,7 @@ static int ftrace_hash_ipmodify_enable(struct ftrace_ops *ops)
if (ftrace_hash_empty(hash))
hash = NULL;

return __ftrace_hash_update_ipmodify(ops, EMPTY_HASH, hash);
return __ftrace_hash_update_ipmodify(ops, EMPTY_HASH, hash, false);
}

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

__ftrace_hash_update_ipmodify(ops, hash, EMPTY_HASH);
__ftrace_hash_update_ipmodify(ops, hash, EMPTY_HASH, false);
}

static int ftrace_hash_ipmodify_update(struct ftrace_ops *ops,
Expand All @@ -2101,7 +2114,7 @@ static int ftrace_hash_ipmodify_update(struct ftrace_ops *ops,
if (ftrace_hash_empty(new_hash))
new_hash = NULL;

return __ftrace_hash_update_ipmodify(ops, old_hash, new_hash);
return __ftrace_hash_update_ipmodify(ops, old_hash, new_hash, false);
}

static void print_ip_ins(const char *fmt, const unsigned char *p)
Expand Down Expand Up @@ -6048,6 +6061,8 @@ int register_ftrace_direct(struct ftrace_ops *ops, unsigned long addr)
ops->direct_call = addr;

err = register_ftrace_function_nolock(ops);
if (err)
remove_direct_functions_hash(hash, addr);

out_unlock:
mutex_unlock(&direct_mutex);
Expand Down Expand Up @@ -6106,7 +6121,7 @@ EXPORT_SYMBOL_GPL(unregister_ftrace_direct);
static int
__modify_ftrace_direct(struct ftrace_ops *ops, unsigned long addr)
{
struct ftrace_hash *hash;
struct ftrace_hash *hash = ops->func_hash->filter_hash;
struct ftrace_func_entry *entry, *iter;
static struct ftrace_ops tmp_ops = {
.func = ftrace_stub,
Expand All @@ -6126,13 +6141,21 @@ __modify_ftrace_direct(struct ftrace_ops *ops, unsigned long addr)
if (err)
return err;

/*
* Call __ftrace_hash_update_ipmodify() here, so that we can call
* ops->ops_func for the ops. This is needed because the above
* register_ftrace_function_nolock() worked on tmp_ops.
*/
err = __ftrace_hash_update_ipmodify(ops, hash, hash, true);
if (err)
goto out;

/*
* Now the ftrace_ops_list_func() is called to do the direct callers.
* We can safely change the direct functions attached to each entry.
*/
mutex_lock(&ftrace_lock);

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

mutex_unlock(&ftrace_lock);

out:
/* Removing the tmp_ops will add the updated direct callers to the functions */
unregister_ftrace_function(&tmp_ops);

Expand Down
3 changes: 3 additions & 0 deletions tools/testing/selftests/bpf/config
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ CONFIG_IPV6_SIT=y
CONFIG_IPV6_TUNNEL=y
CONFIG_KEYS=y
CONFIG_LIRC=y
CONFIG_LIVEPATCH=y
CONFIG_LWTUNNEL=y
CONFIG_MODULE_SIG=y
CONFIG_MODULE_SRCVERSION_ALL=y
Expand Down Expand Up @@ -111,6 +112,8 @@ CONFIG_IP6_NF_FILTER=y
CONFIG_NF_NAT=y
CONFIG_PACKET=y
CONFIG_RC_CORE=y
CONFIG_SAMPLES=y
CONFIG_SAMPLE_LIVEPATCH=m
CONFIG_SECURITY=y
CONFIG_SECURITYFS=y
CONFIG_SYN_COOKIES=y
Expand Down
107 changes: 107 additions & 0 deletions tools/testing/selftests/bpf/prog_tests/livepatch_trampoline.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */

#include <test_progs.h>
#include "testing_helpers.h"
#include "livepatch_trampoline.skel.h"

static int load_livepatch(void)
{
char path[4096];

/* CI will set KBUILD_OUTPUT */
snprintf(path, sizeof(path), "%s/samples/livepatch/livepatch-sample.ko",
getenv("KBUILD_OUTPUT") ? : "../../../..");

return load_module(path, env_verbosity > VERBOSE_NONE);
}

static void unload_livepatch(void)
{
/* Disable the livepatch before unloading the module */
system("echo 0 > /sys/kernel/livepatch/livepatch_sample/enabled");

unload_module("livepatch_sample", env_verbosity > VERBOSE_NONE);
}

static void read_proc_cmdline(void)
{
char buf[4096];
int fd, ret;

fd = open("/proc/cmdline", O_RDONLY);
if (!ASSERT_OK_FD(fd, "open /proc/cmdline"))
return;

ret = read(fd, buf, sizeof(buf));
if (!ASSERT_GT(ret, 0, "read /proc/cmdline"))
goto out;

ASSERT_OK(strncmp(buf, "this has been live patched", 26), "strncmp");

out:
close(fd);
}

static void __test_livepatch_trampoline(bool fexit_first)
{
struct livepatch_trampoline *skel = NULL;
int err;

skel = livepatch_trampoline__open_and_load();
if (!ASSERT_OK_PTR(skel, "skel_open_and_load"))
goto out;

skel->bss->my_pid = getpid();

if (!fexit_first) {
/* fentry program is loaded first by default */
err = livepatch_trampoline__attach(skel);
if (!ASSERT_OK(err, "skel_attach"))
goto out;
} else {
/* Manually load fexit program first. */
skel->links.fexit_cmdline = bpf_program__attach(skel->progs.fexit_cmdline);
if (!ASSERT_OK_PTR(skel->links.fexit_cmdline, "attach_fexit"))
goto out;

skel->links.fentry_cmdline = bpf_program__attach(skel->progs.fentry_cmdline);
if (!ASSERT_OK_PTR(skel->links.fentry_cmdline, "attach_fentry"))
goto out;
}

read_proc_cmdline();

ASSERT_EQ(skel->bss->fentry_hit, 1, "fentry_hit");
ASSERT_EQ(skel->bss->fexit_hit, 1, "fexit_hit");
out:
livepatch_trampoline__destroy(skel);
}

void test_livepatch_trampoline(void)
{
int retry_cnt = 0;

retry:
if (load_livepatch()) {
if (retry_cnt) {
ASSERT_OK(1, "load_livepatch");
goto out;
}
/*
* Something else (previous run of the same test?) loaded
* the KLP module. Unload the KLP module and retry.
*/
unload_livepatch();
retry_cnt++;
goto retry;
}

if (test__start_subtest("fentry_first"))
__test_livepatch_trampoline(false);

if (test__start_subtest("fexit_first"))
__test_livepatch_trampoline(true);
out:
unload_livepatch();
}
30 changes: 30 additions & 0 deletions tools/testing/selftests/bpf/progs/livepatch_trampoline.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

int fentry_hit;
int fexit_hit;
int my_pid;

SEC("fentry/cmdline_proc_show")
int BPF_PROG(fentry_cmdline)
{
if (my_pid != (bpf_get_current_pid_tgid() >> 32))
return 0;

fentry_hit = 1;
return 0;
}

SEC("fexit/cmdline_proc_show")
int BPF_PROG(fexit_cmdline)
{
if (my_pid != (bpf_get_current_pid_tgid() >> 32))
return 0;

fexit_hit = 1;
return 0;
}
Loading