mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-08-09 06:14:34 +02:00
bpf_obj_drop() runs bpf_obj_free_fields() synchronously for
program-allocated objects. When such an object contains NMI unsafe
fields, tracing programs that can run from arbitrary instrumented
context can reach that destruction from unsafe contexts, including NMI.
NMI is likely one instance of this problem, and other instances would
include possible unsafe reentrancy. Deferring bpf_obj_drop() is not
appealing either: it would add delayed-free machinery to a release
operation that otherwise has straightforward synchronous ownership
semantics.
Reject bpf_obj_drop() and bpf_percpu_obj_drop() from tracing programs
that may run from unsafe contexts unless every field in the object's BTF
record is explicitly NMI safe. Do not reject sleepable
BPF_PROG_TYPE_TRACING programs, since they are not the arbitrary/NMI
contexts that motivate the restriction.
Note that while bpf_rb_root and bpf_list_head would be NMI safe on their
own to free, the objects recursively held by them may not be; be
conservative and just mark them as not NMI safe for now.
Use a whitelist for the NMI-safe field set instead of listing only known
NMI unsafe fields. Locks, async fields, unreferenced kptrs, and
refcounts are known to be NMI safe because their destruction is either a
no-op, simple state reset, or async cancellation. Referenced kptrs,
percpu referenced kptrs, uptrs, graph roots, graph nodes, and any future
field type are rejected until audited for arbitrary tracing and NMI
contexts. This is less susceptible to future changes in fields that were
previously safe by exclusion, and to new fields being added without
updating this check.
Convert the existing recursive local-object drop success case to a
syscall program in the same commit, since this verifier change makes the
old tracing program form invalid. The test still exercises
bpf_obj_drop() releasing a referenced task kptr from a safe program
type.
Fixes: ac9f06050a ("bpf: Introduce bpf_obj_drop")
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
Co-developed-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Link: https://lore.kernel.org/r/20260609202548.3571690-2-memxor@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
419 lines
8.3 KiB
C
419 lines
8.3 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */
|
|
|
|
#include <vmlinux.h>
|
|
#include <bpf/bpf_tracing.h>
|
|
#include <bpf/bpf_helpers.h>
|
|
|
|
#include "../bpf_experimental.h"
|
|
#include "task_kfunc_common.h"
|
|
|
|
char _license[] SEC("license") = "GPL";
|
|
|
|
int err, pid;
|
|
|
|
/* Prototype for all of the program trace events below:
|
|
*
|
|
* TRACE_EVENT(task_newtask,
|
|
* TP_PROTO(struct task_struct *p, u64 clone_flags)
|
|
*/
|
|
|
|
struct task_struct *bpf_task_acquire(struct task_struct *p) __ksym __weak;
|
|
|
|
struct task_struct *bpf_task_acquire___one(struct task_struct *task) __ksym __weak;
|
|
/* The two-param bpf_task_acquire doesn't exist */
|
|
struct task_struct *bpf_task_acquire___two(struct task_struct *p, void *ctx) __ksym __weak;
|
|
/* Incorrect type for first param */
|
|
struct task_struct *bpf_task_acquire___three(void *ctx) __ksym __weak;
|
|
|
|
void invalid_kfunc(void) __ksym __weak;
|
|
void bpf_testmod_test_mod_kfunc(int i) __ksym __weak;
|
|
|
|
static bool is_test_kfunc_task(void)
|
|
{
|
|
int cur_pid = bpf_get_current_pid_tgid() >> 32;
|
|
|
|
return pid == cur_pid;
|
|
}
|
|
|
|
static int test_acquire_release(struct task_struct *task)
|
|
{
|
|
struct task_struct *acquired = NULL;
|
|
|
|
if (!bpf_ksym_exists(bpf_task_acquire)) {
|
|
err = 3;
|
|
return 0;
|
|
}
|
|
if (!bpf_ksym_exists(bpf_testmod_test_mod_kfunc)) {
|
|
err = 4;
|
|
return 0;
|
|
}
|
|
if (bpf_ksym_exists(invalid_kfunc)) {
|
|
/* the verifier's dead code elimination should remove this */
|
|
err = 5;
|
|
asm volatile ("goto -1"); /* for (;;); */
|
|
}
|
|
|
|
acquired = bpf_task_acquire(task);
|
|
if (acquired)
|
|
bpf_task_release(acquired);
|
|
else
|
|
err = 6;
|
|
|
|
return 0;
|
|
}
|
|
|
|
SEC("tp_btf/task_newtask")
|
|
int BPF_PROG(test_task_kfunc_flavor_relo, struct task_struct *task, u64 clone_flags)
|
|
{
|
|
struct task_struct *acquired = NULL;
|
|
int fake_ctx = 42;
|
|
|
|
if (bpf_ksym_exists(bpf_task_acquire___one)) {
|
|
acquired = bpf_task_acquire___one(task);
|
|
} else if (bpf_ksym_exists(bpf_task_acquire___two)) {
|
|
/* Here, bpf_object__resolve_ksym_func_btf_id's find_ksym_btf_id
|
|
* call will find vmlinux's bpf_task_acquire, but subsequent
|
|
* bpf_core_types_are_compat will fail
|
|
*/
|
|
acquired = bpf_task_acquire___two(task, &fake_ctx);
|
|
err = 3;
|
|
return 0;
|
|
} else if (bpf_ksym_exists(bpf_task_acquire___three)) {
|
|
/* bpf_core_types_are_compat will fail similarly to above case */
|
|
acquired = bpf_task_acquire___three(&fake_ctx);
|
|
err = 4;
|
|
return 0;
|
|
}
|
|
|
|
if (acquired)
|
|
bpf_task_release(acquired);
|
|
else
|
|
err = 5;
|
|
return 0;
|
|
}
|
|
|
|
SEC("tp_btf/task_newtask")
|
|
int BPF_PROG(test_task_kfunc_flavor_relo_not_found, struct task_struct *task, u64 clone_flags)
|
|
{
|
|
/* Neither symbol should successfully resolve.
|
|
* Success or failure of one ___flavor should not affect others
|
|
*/
|
|
if (bpf_ksym_exists(bpf_task_acquire___two))
|
|
err = 1;
|
|
else if (bpf_ksym_exists(bpf_task_acquire___three))
|
|
err = 2;
|
|
|
|
return 0;
|
|
}
|
|
|
|
SEC("tp_btf/task_newtask")
|
|
int BPF_PROG(test_task_acquire_release_argument, struct task_struct *task, u64 clone_flags)
|
|
{
|
|
if (!is_test_kfunc_task())
|
|
return 0;
|
|
|
|
return test_acquire_release(task);
|
|
}
|
|
|
|
SEC("tp_btf/task_newtask")
|
|
int BPF_PROG(test_task_acquire_release_current, struct task_struct *task, u64 clone_flags)
|
|
{
|
|
if (!is_test_kfunc_task())
|
|
return 0;
|
|
|
|
return test_acquire_release(bpf_get_current_task_btf());
|
|
}
|
|
|
|
SEC("tp_btf/task_newtask")
|
|
int BPF_PROG(test_task_acquire_leave_in_map, struct task_struct *task, u64 clone_flags)
|
|
{
|
|
long status;
|
|
|
|
if (!is_test_kfunc_task())
|
|
return 0;
|
|
|
|
status = tasks_kfunc_map_insert(task);
|
|
if (status)
|
|
err = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
SEC("syscall")
|
|
int test_task_xchg_release(const void *ctx)
|
|
{
|
|
struct task_struct *task, *kptr, *acquired;
|
|
struct __tasks_kfunc_map_value *v, *local;
|
|
int refcnt, refcnt_after_drop;
|
|
long status;
|
|
|
|
(void)ctx;
|
|
|
|
task = bpf_get_current_task_btf();
|
|
status = tasks_kfunc_map_insert(task);
|
|
if (status) {
|
|
err = 1;
|
|
return 0;
|
|
}
|
|
|
|
v = tasks_kfunc_map_value_lookup(task);
|
|
if (!v) {
|
|
err = 2;
|
|
return 0;
|
|
}
|
|
|
|
kptr = bpf_kptr_xchg(&v->task, NULL);
|
|
if (!kptr) {
|
|
err = 3;
|
|
return 0;
|
|
}
|
|
|
|
local = bpf_obj_new(typeof(*local));
|
|
if (!local) {
|
|
err = 4;
|
|
bpf_task_release(kptr);
|
|
return 0;
|
|
}
|
|
|
|
kptr = bpf_kptr_xchg(&local->task, kptr);
|
|
if (kptr) {
|
|
err = 5;
|
|
bpf_obj_drop(local);
|
|
bpf_task_release(kptr);
|
|
return 0;
|
|
}
|
|
|
|
kptr = bpf_kptr_xchg(&local->task, NULL);
|
|
if (!kptr) {
|
|
err = 6;
|
|
bpf_obj_drop(local);
|
|
return 0;
|
|
}
|
|
|
|
/* Stash a copy into local kptr and check if it is released recursively. */
|
|
acquired = bpf_task_acquire(kptr);
|
|
if (!acquired) {
|
|
err = 7;
|
|
bpf_obj_drop(local);
|
|
bpf_task_release(kptr);
|
|
return 0;
|
|
}
|
|
bpf_probe_read_kernel(&refcnt, sizeof(refcnt), &acquired->rcu_users);
|
|
|
|
acquired = bpf_kptr_xchg(&local->task, acquired);
|
|
if (acquired) {
|
|
err = 8;
|
|
bpf_obj_drop(local);
|
|
bpf_task_release(kptr);
|
|
bpf_task_release(acquired);
|
|
return 0;
|
|
}
|
|
|
|
bpf_obj_drop(local);
|
|
|
|
bpf_probe_read_kernel(&refcnt_after_drop, sizeof(refcnt_after_drop), &kptr->rcu_users);
|
|
if (refcnt != refcnt_after_drop + 1) {
|
|
err = 9;
|
|
bpf_task_release(kptr);
|
|
return 0;
|
|
}
|
|
|
|
bpf_task_release(kptr);
|
|
return 0;
|
|
}
|
|
|
|
SEC("tp_btf/task_newtask")
|
|
int BPF_PROG(test_task_map_acquire_release, struct task_struct *task, u64 clone_flags)
|
|
{
|
|
struct task_struct *kptr;
|
|
struct __tasks_kfunc_map_value *v;
|
|
long status;
|
|
|
|
if (!is_test_kfunc_task())
|
|
return 0;
|
|
|
|
status = tasks_kfunc_map_insert(task);
|
|
if (status) {
|
|
err = 1;
|
|
return 0;
|
|
}
|
|
|
|
v = tasks_kfunc_map_value_lookup(task);
|
|
if (!v) {
|
|
err = 2;
|
|
return 0;
|
|
}
|
|
|
|
bpf_rcu_read_lock();
|
|
kptr = v->task;
|
|
if (!kptr) {
|
|
err = 3;
|
|
} else {
|
|
kptr = bpf_task_acquire(kptr);
|
|
if (!kptr)
|
|
err = 4;
|
|
else
|
|
bpf_task_release(kptr);
|
|
}
|
|
bpf_rcu_read_unlock();
|
|
|
|
return 0;
|
|
}
|
|
|
|
SEC("tp_btf/task_newtask")
|
|
int BPF_PROG(test_task_current_acquire_release, struct task_struct *task, u64 clone_flags)
|
|
{
|
|
struct task_struct *current, *acquired;
|
|
|
|
if (!is_test_kfunc_task())
|
|
return 0;
|
|
|
|
current = bpf_get_current_task_btf();
|
|
acquired = bpf_task_acquire(current);
|
|
if (acquired)
|
|
bpf_task_release(acquired);
|
|
else
|
|
err = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void lookup_compare_pid(const struct task_struct *p)
|
|
{
|
|
struct task_struct *acquired;
|
|
|
|
acquired = bpf_task_from_pid(p->pid);
|
|
if (!acquired) {
|
|
err = 1;
|
|
return;
|
|
}
|
|
|
|
if (acquired->pid != p->pid)
|
|
err = 2;
|
|
bpf_task_release(acquired);
|
|
}
|
|
|
|
SEC("tp_btf/task_newtask")
|
|
int BPF_PROG(test_task_from_pid_arg, struct task_struct *task, u64 clone_flags)
|
|
{
|
|
if (!is_test_kfunc_task())
|
|
return 0;
|
|
|
|
lookup_compare_pid(task);
|
|
return 0;
|
|
}
|
|
|
|
SEC("tp_btf/task_newtask")
|
|
int BPF_PROG(test_task_from_pid_current, struct task_struct *task, u64 clone_flags)
|
|
{
|
|
if (!is_test_kfunc_task())
|
|
return 0;
|
|
|
|
lookup_compare_pid(bpf_get_current_task_btf());
|
|
return 0;
|
|
}
|
|
|
|
static int is_pid_lookup_valid(s32 pid)
|
|
{
|
|
struct task_struct *acquired;
|
|
|
|
acquired = bpf_task_from_pid(pid);
|
|
if (acquired) {
|
|
bpf_task_release(acquired);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
SEC("tp_btf/task_newtask")
|
|
int BPF_PROG(test_task_from_pid_invalid, struct task_struct *task, u64 clone_flags)
|
|
{
|
|
if (!is_test_kfunc_task())
|
|
return 0;
|
|
|
|
bpf_strncmp(task->comm, 12, "foo");
|
|
bpf_strncmp(task->comm, 16, "foo");
|
|
bpf_strncmp(&task->comm[8], 4, "foo");
|
|
|
|
if (is_pid_lookup_valid(-1)) {
|
|
err = 1;
|
|
return 0;
|
|
}
|
|
|
|
if (is_pid_lookup_valid(0xcafef00d)) {
|
|
err = 2;
|
|
return 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
SEC("tp_btf/task_newtask")
|
|
int BPF_PROG(task_kfunc_acquire_trusted_walked, struct task_struct *task, u64 clone_flags)
|
|
{
|
|
struct task_struct *acquired;
|
|
|
|
/* task->group_leader is listed as a trusted, non-NULL field of task struct. */
|
|
acquired = bpf_task_acquire(task->group_leader);
|
|
if (acquired)
|
|
bpf_task_release(acquired);
|
|
else
|
|
err = 1;
|
|
|
|
|
|
return 0;
|
|
}
|
|
|
|
SEC("syscall")
|
|
int test_task_from_vpid_current(const void *ctx)
|
|
{
|
|
struct task_struct *current, *v_task;
|
|
|
|
v_task = bpf_task_from_vpid(1);
|
|
if (!v_task) {
|
|
err = 1;
|
|
return 0;
|
|
}
|
|
|
|
current = bpf_get_current_task_btf();
|
|
|
|
/* The current process should be the init process (pid 1) in the new pid namespace. */
|
|
if (current != v_task)
|
|
err = 2;
|
|
|
|
bpf_task_release(v_task);
|
|
return 0;
|
|
}
|
|
|
|
SEC("syscall")
|
|
int test_task_from_vpid_invalid(const void *ctx)
|
|
{
|
|
struct task_struct *v_task;
|
|
|
|
v_task = bpf_task_from_vpid(-1);
|
|
if (v_task) {
|
|
err = 1;
|
|
goto err;
|
|
}
|
|
|
|
/* There should be only one process (current process) in the new pid namespace. */
|
|
v_task = bpf_task_from_vpid(2);
|
|
if (v_task) {
|
|
err = 2;
|
|
goto err;
|
|
}
|
|
|
|
v_task = bpf_task_from_vpid(9999);
|
|
if (v_task) {
|
|
err = 3;
|
|
goto err;
|
|
}
|
|
|
|
return 0;
|
|
err:
|
|
bpf_task_release(v_task);
|
|
return 0;
|
|
}
|