mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-10-26 10:57:38 +08:00
Pull bpf updates from Alexei Starovoitov:
"Major changes:
- Introduce scalar evolution (SCEV) analysis in the verifier.
It infers how registers and stack slots change in a loop and lets
the verifier check bounded loops with induction variables as ranges
instead of walking every iteration. The number of processed insns
drops ten fold in programs with loops (Eduard Zingerman)
- Add 'callx' instruction: indirect call of a static BPF subprogram.
The address is taken with ld_imm64 or loaded from read-only data.
Supported by x86-64, arm64 and riscv64 JITs (Alexei Starovoitov, Pu
Lehui)
- Add unwind_goto and unwind_exit instructions, so that Rust panic or
C++ exception can run destructors of every frame it passes through.
The normal path is free of unwind code. Supported by x86-64 and
arm64 JITs (Alexei Starovoitov)
- Support programs compiled by rust-bpf: treat load and store through
a number as arena access with BPF_F_ARENA_SCALAR, accept rust names
in BTF (Alexei Starovoitov)
- Raise BPF program stack size from 512 bytes to 2 KiB on x86-64 and
arm64 (Kumar Kartikeya Dwivedi)
- Support struct, union and __int128 up to 16 bytes passed and
returned by value in BPF subprograms and kfuncs, and fix x86-64 and
arm64 trampolines for such arguments (Yonghong Song)
- Introduce a common way to attach struct_ops to a cgroup via
bpf_link. bpf_tcp_ops is the first user and mirrors TCP sock_ops
callbacks (Martin KaFai Lau, Amery Hung)
- Describe inlined functions in BTF. New kinds tell where the inline
sites are and how to fetch the arguments there.
CONFIG_DEBUG_INFO_BTF_INLINE keeps it apart from the main BTF in
.BTF.inline section or in btf_vmlinux_inline.ko (Alan Maguire)
- Fix many memory safety, lifetime and locking bugs in BPF core,
verifier, iterators, local storage, dynptr, devmap, cpumap and
seg6local (Josef Bacik, Alexei Starovoitov)
- Diff stat is dominated by selftests and by removal of
lib/test_bpf.c. Its 16k lines of instruction tests were converted
to BPF selftests that BPF CI runs on every architecture it covers
(Daniel Borkmann)
Other features and fixes:
- Add KASAN checks to memory accesses of JITed programs on x86-64
(Alexis Lothoré)
- Complete unification of helper, kfunc and subprog argument checks
in the verifier (Amery Hung)
- Add bpf_copy_from_user_mm[_str]() kfuncs (Anastasios Papagiannis)
- Various features and fixes (Daniel Borkmann):
- Add .bpf keyring for program signature verification, modelled
after dm-verity keyring, and support ML-DSA in signing tools
- Emit shorter and faster code in x86-64 JIT for patterns that
LLVM generates
- Fix various issues in gotox handling
- Let BPF LSM set xattrs at inode creation with bpf_inode_init_xattr()
(David Windsor, Daniel Borkmann)
- Use sleepable page allocation in sleepable arena paths. Fix races
between arena free, allocation and user page fault (Emil Tsalapatis)
- Switch BPF crypto from lskcipher to AES-CBC and AES-ECB libraries
(Eric Biggers)
- Add bpf_proactive_reclaim() kfunc (Hui Zhu)
- Render decl_tags in libbpf btf_dump and add tests for bpftool C
dump (Ihor Solodrai)
- Speed up tracing_multi attach on x86 (Jiri Olsa)
- Various features and fixes (Kumar Kartikeya Dwivedi):
- Add BPF_PROG_STREAM_OPEN command to read BPF streams through a
file descriptor with poll support
- Let kernel structs publish arena pointers with __arena type tag
- Fix __uninit kfunc output buffers and fastcall rewrite with
indirect stack accesses
- Refuse CO-RE relocations for signed programs
- Add enqueue_rcvq and dequeue_rcvq hooks to bpf_tcp_ops to adjust
sk_rcvlowat and avoid unnecessary wakeups (Kuniyuki Iwashima)
- Add bpf_ksock_sendto() kfunc and allow ksock connect and send from
bpf_wq (Mahe Tardy)
- Fix racy plt target update in arm64 bpf_arch_text_poke()
(Matthew Wood)
- Restructure BPF selftests Makefile as a layered build
(Mykola Lysenko)
- Speed up hash map lookups for u32 and u64 keys (Mykyta Yatsenko)
- Add signed div/mod, movsx, bswap and gotol to MIPS JITs, signed
div/mod to parisc JITs (Nicholas Dudar, Johan Almbladh)
- Fix stack overflow in build ID parser on 32-bit (Nickolai
Zeldovich)
- Add stack arguments, signed arena loads and gotox to riscv64 JIT.
Pu Lehui is now co-maintainer of riscv BPF JITs (Pu Lehui, Chen
Pei, Feng Jiang)
- Add bpf_call_rcu() and bpf_call_rcu_tasks_trace() kfuncs. Support
fetching and/or/xor atomics in arena on x86-64 (Puranjay Mohan)
- Fix flaky selftests, selftests with clang 23 and bpf-gcc
(Qiliang Yuan, KaFai Wan, Vineet Gupta)
- Fix builds broken by fatal resolve_btfids warnings, BTF generation
on ARC, and define arch_cmpxchg_local() on arc, hexagon and
openrisc (Sasha Levin)
- Reduce memory use of hash maps by up to 50% per element
(T.J. Mercier)
- Mark pointer fields of sched_ext ops arguments as trusted (Tejun
Heo)
- Remove legacy bpf_asm, bpf_dbg and bpf_jit_disasm tools (Xia Yan)
- Fix index overflow in queue and stack maps, arena range tree after
allocation failure, tail call loop after freplace detach, lost
signal in bpf_send_signal() (Yuan Chen)
- Many more fixes and cleanups"
* tag 'bpf-next-7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next: (660 commits)
selftests/bpf: Time tc_edt from its first packet with data
selftests/bpf: Test unwind_exit of an exception callback
selftests/bpf: Tests for 32-bit scalar evolution bounds computation
bpf: Reject unwind_goto and unwind_exit in offloaded progs
bpf: Support 32-bit linear scalar evolution
bpf: Make unwind_exit of an exception callback exit in JITs
selftests/bpf: Test forbidden bpf_ksock_sendto() LSM attach
selftests/bpf: Test ksock sendto()
bpf: Add bpf_ksock_sendto() kfunc
selftests/bpf: Test unwinding from R0: R2 subprog
selftests/bpf: Add a C++ prog with exceptions
selftests/bpf: Add tests for unwind_goto and unwind_exit
bpf, arm64: Support unwind_goto and unwind_exit
bpf, x86: Support unwind_goto and unwind_exit
bpf: Add unwind_goto and unwind_exit insns
tools/bpf: Remove legacy BPF tools
bpf: Annotate data races on bpf_lru_node type
selftests/bpf: Add arena allocation race tests
bpf: Fix arena race between page free and alloc leading to incoherency
bpf: Revalidate faulted arena pages before installing user mappings
...
1238 lines
30 KiB
C
1238 lines
30 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/* Copyright (c) 2020 Facebook */
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/namei.h>
|
|
#include <linux/pid_namespace.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/filter.h>
|
|
#include <linux/bpf_mem_alloc.h>
|
|
#include <linux/btf_ids.h>
|
|
#include <linux/mm_types.h>
|
|
#include <linux/mmap_lock.h>
|
|
#include <linux/sched/mm.h>
|
|
#include "mmap_unlock_work.h"
|
|
|
|
static const char * const iter_task_type_names[] = {
|
|
"ALL",
|
|
"TID",
|
|
"PID",
|
|
};
|
|
|
|
struct bpf_iter_seq_task_common {
|
|
struct pid_namespace *ns;
|
|
enum bpf_iter_task_type type;
|
|
u32 pid;
|
|
u32 pid_visiting;
|
|
};
|
|
|
|
struct bpf_iter_seq_task_info {
|
|
/* The first field must be struct bpf_iter_seq_task_common.
|
|
* this is assumed by {init, fini}_seq_pidns() callback functions.
|
|
*/
|
|
struct bpf_iter_seq_task_common common;
|
|
u32 tid;
|
|
};
|
|
|
|
static struct task_struct *task_group_seq_get_next(struct bpf_iter_seq_task_common *common,
|
|
u32 *tid,
|
|
bool skip_if_dup_files)
|
|
{
|
|
struct task_struct *task;
|
|
struct pid *pid;
|
|
u32 next_tid;
|
|
|
|
if (!*tid) {
|
|
/* The first time, the iterator calls this function. */
|
|
pid = find_pid_ns(common->pid, common->ns);
|
|
task = get_pid_task(pid, PIDTYPE_TGID);
|
|
if (!task)
|
|
return NULL;
|
|
|
|
*tid = common->pid;
|
|
common->pid_visiting = common->pid;
|
|
|
|
return task;
|
|
}
|
|
|
|
/* If the control returns to user space and comes back to the
|
|
* kernel again, *tid and common->pid_visiting should be the
|
|
* same for task_seq_start() to pick up the correct task.
|
|
*/
|
|
if (*tid == common->pid_visiting) {
|
|
pid = find_pid_ns(common->pid_visiting, common->ns);
|
|
task = get_pid_task(pid, PIDTYPE_PID);
|
|
|
|
return task;
|
|
}
|
|
|
|
task = find_task_by_pid_ns(common->pid_visiting, common->ns);
|
|
if (!task)
|
|
return NULL;
|
|
|
|
retry:
|
|
task = __next_thread(task);
|
|
if (!task)
|
|
return NULL;
|
|
|
|
next_tid = __task_pid_nr_ns(task, PIDTYPE_PID, common->ns);
|
|
if (!next_tid)
|
|
goto retry;
|
|
|
|
if (skip_if_dup_files && task->files == task->group_leader->files)
|
|
goto retry;
|
|
|
|
*tid = common->pid_visiting = next_tid;
|
|
get_task_struct(task);
|
|
return task;
|
|
}
|
|
|
|
static struct task_struct *task_seq_get_next(struct bpf_iter_seq_task_common *common,
|
|
u32 *tid,
|
|
bool skip_if_dup_files)
|
|
{
|
|
struct task_struct *task = NULL;
|
|
struct pid *pid;
|
|
|
|
if (common->type == BPF_TASK_ITER_TID) {
|
|
if (*tid && *tid != common->pid)
|
|
return NULL;
|
|
rcu_read_lock();
|
|
pid = find_pid_ns(common->pid, common->ns);
|
|
if (pid) {
|
|
task = get_pid_task(pid, PIDTYPE_PID);
|
|
*tid = common->pid;
|
|
}
|
|
rcu_read_unlock();
|
|
|
|
return task;
|
|
}
|
|
|
|
if (common->type == BPF_TASK_ITER_TGID) {
|
|
rcu_read_lock();
|
|
task = task_group_seq_get_next(common, tid, skip_if_dup_files);
|
|
rcu_read_unlock();
|
|
|
|
return task;
|
|
}
|
|
|
|
rcu_read_lock();
|
|
retry:
|
|
pid = find_ge_pid(*tid, common->ns);
|
|
if (pid) {
|
|
*tid = pid_nr_ns(pid, common->ns);
|
|
task = get_pid_task(pid, PIDTYPE_PID);
|
|
if (!task) {
|
|
++*tid;
|
|
goto retry;
|
|
} else if (skip_if_dup_files && !thread_group_leader(task) &&
|
|
task->files == task->group_leader->files) {
|
|
put_task_struct(task);
|
|
task = NULL;
|
|
++*tid;
|
|
goto retry;
|
|
}
|
|
}
|
|
rcu_read_unlock();
|
|
|
|
return task;
|
|
}
|
|
|
|
static void *task_seq_start(struct seq_file *seq, loff_t *pos)
|
|
{
|
|
struct bpf_iter_seq_task_info *info = seq->private;
|
|
struct task_struct *task;
|
|
|
|
task = task_seq_get_next(&info->common, &info->tid, false);
|
|
if (!task)
|
|
return NULL;
|
|
|
|
if (*pos == 0)
|
|
++*pos;
|
|
return task;
|
|
}
|
|
|
|
static void *task_seq_next(struct seq_file *seq, void *v, loff_t *pos)
|
|
{
|
|
struct bpf_iter_seq_task_info *info = seq->private;
|
|
struct task_struct *task;
|
|
|
|
++*pos;
|
|
++info->tid;
|
|
put_task_struct((struct task_struct *)v);
|
|
task = task_seq_get_next(&info->common, &info->tid, false);
|
|
if (!task)
|
|
return NULL;
|
|
|
|
return task;
|
|
}
|
|
|
|
struct bpf_iter__task {
|
|
__bpf_md_ptr(struct bpf_iter_meta *, meta);
|
|
__bpf_md_ptr(struct task_struct *, task);
|
|
};
|
|
|
|
DEFINE_BPF_ITER_FUNC(task, struct bpf_iter_meta *meta, struct task_struct *task)
|
|
|
|
static int __task_seq_show(struct seq_file *seq, struct task_struct *task,
|
|
bool in_stop)
|
|
{
|
|
struct bpf_iter_meta meta;
|
|
struct bpf_iter__task ctx;
|
|
struct bpf_prog *prog;
|
|
|
|
meta.seq = seq;
|
|
prog = bpf_iter_get_info(&meta, in_stop);
|
|
if (!prog)
|
|
return 0;
|
|
|
|
ctx.meta = &meta;
|
|
ctx.task = task;
|
|
return bpf_iter_run_prog(prog, &ctx);
|
|
}
|
|
|
|
static int task_seq_show(struct seq_file *seq, void *v)
|
|
{
|
|
return __task_seq_show(seq, v, false);
|
|
}
|
|
|
|
static void task_seq_stop(struct seq_file *seq, void *v)
|
|
{
|
|
if (!v)
|
|
(void)__task_seq_show(seq, v, true);
|
|
else
|
|
put_task_struct((struct task_struct *)v);
|
|
}
|
|
|
|
static int bpf_iter_attach_task(struct bpf_prog *prog,
|
|
union bpf_iter_link_info *linfo,
|
|
struct bpf_iter_aux_info *aux)
|
|
{
|
|
unsigned int flags;
|
|
struct pid *pid;
|
|
pid_t tgid;
|
|
|
|
if ((!!linfo->task.tid + !!linfo->task.pid + !!linfo->task.pid_fd) > 1)
|
|
return -EINVAL;
|
|
|
|
aux->task.type = BPF_TASK_ITER_ALL;
|
|
if (linfo->task.tid != 0) {
|
|
aux->task.type = BPF_TASK_ITER_TID;
|
|
aux->task.pid = linfo->task.tid;
|
|
}
|
|
if (linfo->task.pid != 0) {
|
|
aux->task.type = BPF_TASK_ITER_TGID;
|
|
aux->task.pid = linfo->task.pid;
|
|
}
|
|
if (linfo->task.pid_fd != 0) {
|
|
aux->task.type = BPF_TASK_ITER_TGID;
|
|
|
|
pid = pidfd_get_pid(linfo->task.pid_fd, &flags);
|
|
if (IS_ERR(pid))
|
|
return PTR_ERR(pid);
|
|
|
|
tgid = pid_nr_ns(pid, task_active_pid_ns(current));
|
|
aux->task.pid = tgid;
|
|
put_pid(pid);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct seq_operations task_seq_ops = {
|
|
.start = task_seq_start,
|
|
.next = task_seq_next,
|
|
.stop = task_seq_stop,
|
|
.show = task_seq_show,
|
|
};
|
|
|
|
struct bpf_iter_seq_task_file_info {
|
|
/* The first field must be struct bpf_iter_seq_task_common.
|
|
* this is assumed by {init, fini}_seq_pidns() callback functions.
|
|
*/
|
|
struct bpf_iter_seq_task_common common;
|
|
struct task_struct *task;
|
|
u32 tid;
|
|
u32 fd;
|
|
};
|
|
|
|
static struct file *
|
|
task_file_seq_get_next(struct bpf_iter_seq_task_file_info *info)
|
|
{
|
|
u32 saved_tid = info->tid;
|
|
struct task_struct *curr_task;
|
|
unsigned int curr_fd = info->fd;
|
|
struct file *f;
|
|
|
|
/* If this function returns a non-NULL file object,
|
|
* it held a reference to the task/file.
|
|
* Otherwise, it does not hold any reference.
|
|
*/
|
|
again:
|
|
if (info->task) {
|
|
curr_task = info->task;
|
|
curr_fd = info->fd;
|
|
} else {
|
|
curr_task = task_seq_get_next(&info->common, &info->tid, true);
|
|
if (!curr_task) {
|
|
info->task = NULL;
|
|
return NULL;
|
|
}
|
|
|
|
/* set info->task */
|
|
info->task = curr_task;
|
|
if (saved_tid == info->tid)
|
|
curr_fd = info->fd;
|
|
else
|
|
curr_fd = 0;
|
|
}
|
|
|
|
f = fget_task_next(curr_task, &curr_fd);
|
|
if (f) {
|
|
/* set info->fd */
|
|
info->fd = curr_fd;
|
|
return f;
|
|
}
|
|
|
|
/* the current task is done, go to the next task */
|
|
put_task_struct(curr_task);
|
|
|
|
if (info->common.type == BPF_TASK_ITER_TID) {
|
|
info->task = NULL;
|
|
return NULL;
|
|
}
|
|
|
|
info->task = NULL;
|
|
info->fd = 0;
|
|
saved_tid = ++(info->tid);
|
|
goto again;
|
|
}
|
|
|
|
static void *task_file_seq_start(struct seq_file *seq, loff_t *pos)
|
|
{
|
|
struct bpf_iter_seq_task_file_info *info = seq->private;
|
|
struct file *file;
|
|
|
|
info->task = NULL;
|
|
file = task_file_seq_get_next(info);
|
|
if (file && *pos == 0)
|
|
++*pos;
|
|
|
|
return file;
|
|
}
|
|
|
|
static void *task_file_seq_next(struct seq_file *seq, void *v, loff_t *pos)
|
|
{
|
|
struct bpf_iter_seq_task_file_info *info = seq->private;
|
|
|
|
++*pos;
|
|
++info->fd;
|
|
fput((struct file *)v);
|
|
return task_file_seq_get_next(info);
|
|
}
|
|
|
|
struct bpf_iter__task_file {
|
|
__bpf_md_ptr(struct bpf_iter_meta *, meta);
|
|
__bpf_md_ptr(struct task_struct *, task);
|
|
u32 fd __aligned(8);
|
|
__bpf_md_ptr(struct file *, file);
|
|
};
|
|
|
|
DEFINE_BPF_ITER_FUNC(task_file, struct bpf_iter_meta *meta,
|
|
struct task_struct *task, u32 fd,
|
|
struct file *file)
|
|
|
|
static int __task_file_seq_show(struct seq_file *seq, struct file *file,
|
|
bool in_stop)
|
|
{
|
|
struct bpf_iter_seq_task_file_info *info = seq->private;
|
|
struct bpf_iter__task_file ctx;
|
|
struct bpf_iter_meta meta;
|
|
struct bpf_prog *prog;
|
|
|
|
meta.seq = seq;
|
|
prog = bpf_iter_get_info(&meta, in_stop);
|
|
if (!prog)
|
|
return 0;
|
|
|
|
ctx.meta = &meta;
|
|
ctx.task = info->task;
|
|
ctx.fd = info->fd;
|
|
ctx.file = file;
|
|
return bpf_iter_run_prog(prog, &ctx);
|
|
}
|
|
|
|
static int task_file_seq_show(struct seq_file *seq, void *v)
|
|
{
|
|
return __task_file_seq_show(seq, v, false);
|
|
}
|
|
|
|
static void task_file_seq_stop(struct seq_file *seq, void *v)
|
|
{
|
|
struct bpf_iter_seq_task_file_info *info = seq->private;
|
|
|
|
if (!v) {
|
|
(void)__task_file_seq_show(seq, v, true);
|
|
} else {
|
|
fput((struct file *)v);
|
|
put_task_struct(info->task);
|
|
info->task = NULL;
|
|
}
|
|
}
|
|
|
|
static int init_seq_pidns(void *priv_data, struct bpf_iter_aux_info *aux)
|
|
{
|
|
struct bpf_iter_seq_task_common *common = priv_data;
|
|
|
|
common->ns = get_pid_ns(task_active_pid_ns(current));
|
|
common->type = aux->task.type;
|
|
common->pid = aux->task.pid;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void fini_seq_pidns(void *priv_data)
|
|
{
|
|
struct bpf_iter_seq_task_common *common = priv_data;
|
|
|
|
put_pid_ns(common->ns);
|
|
}
|
|
|
|
static const struct seq_operations task_file_seq_ops = {
|
|
.start = task_file_seq_start,
|
|
.next = task_file_seq_next,
|
|
.stop = task_file_seq_stop,
|
|
.show = task_file_seq_show,
|
|
};
|
|
|
|
struct bpf_iter_seq_task_vma_info {
|
|
/* The first field must be struct bpf_iter_seq_task_common.
|
|
* this is assumed by {init, fini}_seq_pidns() callback functions.
|
|
*/
|
|
struct bpf_iter_seq_task_common common;
|
|
struct task_struct *task;
|
|
struct mm_struct *mm;
|
|
struct vm_area_struct *vma;
|
|
u32 tid;
|
|
unsigned long prev_vm_start;
|
|
unsigned long prev_vm_end;
|
|
};
|
|
|
|
enum bpf_task_vma_iter_find_op {
|
|
task_vma_iter_first_vma, /* use find_vma() with addr 0 */
|
|
task_vma_iter_next_vma, /* use vma_next() with curr_vma */
|
|
task_vma_iter_find_vma, /* use find_vma() to find next vma */
|
|
};
|
|
|
|
static struct vm_area_struct *
|
|
task_vma_seq_get_next(struct bpf_iter_seq_task_vma_info *info)
|
|
{
|
|
enum bpf_task_vma_iter_find_op op;
|
|
struct vm_area_struct *curr_vma;
|
|
struct task_struct *curr_task;
|
|
struct mm_struct *curr_mm;
|
|
u32 saved_tid = info->tid;
|
|
|
|
/* If this function returns a non-NULL vma, it holds a reference to
|
|
* the task_struct, holds a refcount on mm->mm_users, and holds
|
|
* read lock on vma->mm->mmap_lock.
|
|
* If this function returns NULL, it does not hold any reference or
|
|
* lock.
|
|
*/
|
|
if (info->task) {
|
|
curr_task = info->task;
|
|
curr_vma = info->vma;
|
|
curr_mm = info->mm;
|
|
/* In case of lock contention, drop mmap_lock to unblock
|
|
* the writer.
|
|
*
|
|
* After relock, call find(mm, prev_vm_end - 1) to find
|
|
* new vma to process.
|
|
*
|
|
* +------+------+-----------+
|
|
* | VMA1 | VMA2 | VMA3 |
|
|
* +------+------+-----------+
|
|
* | | | |
|
|
* 4k 8k 16k 400k
|
|
*
|
|
* For example, curr_vma == VMA2. Before unlock, we set
|
|
*
|
|
* prev_vm_start = 8k
|
|
* prev_vm_end = 16k
|
|
*
|
|
* There are a few cases:
|
|
*
|
|
* 1) VMA2 is freed, but VMA3 exists.
|
|
*
|
|
* find_vma() will return VMA3, just process VMA3.
|
|
*
|
|
* 2) VMA2 still exists.
|
|
*
|
|
* find_vma() will return VMA2, process VMA2->next.
|
|
*
|
|
* 3) no more vma in this mm.
|
|
*
|
|
* Process the next task.
|
|
*
|
|
* 4) find_vma() returns a different vma, VMA2'.
|
|
*
|
|
* 4.1) If VMA2 covers same range as VMA2', skip VMA2',
|
|
* because we already covered the range;
|
|
* 4.2) VMA2 and VMA2' covers different ranges, process
|
|
* VMA2'.
|
|
*/
|
|
if (mmap_lock_is_contended(curr_mm)) {
|
|
info->prev_vm_start = curr_vma->vm_start;
|
|
info->prev_vm_end = curr_vma->vm_end;
|
|
op = task_vma_iter_find_vma;
|
|
mmap_read_unlock(curr_mm);
|
|
if (mmap_read_lock_killable(curr_mm)) {
|
|
mmput(curr_mm);
|
|
goto finish;
|
|
}
|
|
} else {
|
|
op = task_vma_iter_next_vma;
|
|
}
|
|
} else {
|
|
again:
|
|
curr_task = task_seq_get_next(&info->common, &info->tid, true);
|
|
if (!curr_task) {
|
|
info->tid++;
|
|
goto finish;
|
|
}
|
|
|
|
if (saved_tid != info->tid) {
|
|
/* new task, process the first vma */
|
|
op = task_vma_iter_first_vma;
|
|
} else {
|
|
/* Found the same tid, which means the user space
|
|
* finished data in previous buffer and read more.
|
|
* We dropped mmap_lock before returning to user
|
|
* space, so it is necessary to use find_vma() to
|
|
* find the next vma to process.
|
|
*/
|
|
op = task_vma_iter_find_vma;
|
|
}
|
|
|
|
curr_mm = get_task_mm(curr_task);
|
|
if (!curr_mm)
|
|
goto next_task;
|
|
|
|
if (mmap_read_lock_killable(curr_mm)) {
|
|
mmput(curr_mm);
|
|
goto finish;
|
|
}
|
|
}
|
|
|
|
switch (op) {
|
|
case task_vma_iter_first_vma:
|
|
curr_vma = find_vma(curr_mm, 0);
|
|
break;
|
|
case task_vma_iter_next_vma:
|
|
curr_vma = find_vma(curr_mm, curr_vma->vm_end);
|
|
break;
|
|
case task_vma_iter_find_vma:
|
|
/* We dropped mmap_lock so it is necessary to use find_vma
|
|
* to find the next vma. This is similar to the mechanism
|
|
* in show_smaps_rollup().
|
|
*/
|
|
curr_vma = find_vma(curr_mm, info->prev_vm_end - 1);
|
|
/* case 1) and 4.2) above just use curr_vma */
|
|
|
|
/* check for case 2) or case 4.1) above */
|
|
if (curr_vma &&
|
|
curr_vma->vm_start == info->prev_vm_start &&
|
|
curr_vma->vm_end == info->prev_vm_end)
|
|
curr_vma = find_vma(curr_mm, curr_vma->vm_end);
|
|
break;
|
|
}
|
|
if (!curr_vma) {
|
|
/* case 3) above, or case 2) 4.1) with vma->next == NULL */
|
|
mmap_read_unlock(curr_mm);
|
|
mmput(curr_mm);
|
|
goto next_task;
|
|
}
|
|
info->task = curr_task;
|
|
info->vma = curr_vma;
|
|
info->mm = curr_mm;
|
|
return curr_vma;
|
|
|
|
next_task:
|
|
if (info->common.type == BPF_TASK_ITER_TID)
|
|
goto finish;
|
|
|
|
put_task_struct(curr_task);
|
|
info->task = NULL;
|
|
info->mm = NULL;
|
|
info->tid++;
|
|
goto again;
|
|
|
|
finish:
|
|
if (curr_task)
|
|
put_task_struct(curr_task);
|
|
info->task = NULL;
|
|
info->vma = NULL;
|
|
info->mm = NULL;
|
|
return NULL;
|
|
}
|
|
|
|
static void *task_vma_seq_start(struct seq_file *seq, loff_t *pos)
|
|
{
|
|
struct bpf_iter_seq_task_vma_info *info = seq->private;
|
|
struct vm_area_struct *vma;
|
|
|
|
vma = task_vma_seq_get_next(info);
|
|
if (vma && *pos == 0)
|
|
++*pos;
|
|
|
|
return vma;
|
|
}
|
|
|
|
static void *task_vma_seq_next(struct seq_file *seq, void *v, loff_t *pos)
|
|
{
|
|
struct bpf_iter_seq_task_vma_info *info = seq->private;
|
|
|
|
++*pos;
|
|
return task_vma_seq_get_next(info);
|
|
}
|
|
|
|
struct bpf_iter__task_vma {
|
|
__bpf_md_ptr(struct bpf_iter_meta *, meta);
|
|
__bpf_md_ptr(struct task_struct *, task);
|
|
__bpf_md_ptr(struct vm_area_struct *, vma);
|
|
};
|
|
|
|
DEFINE_BPF_ITER_FUNC(task_vma, struct bpf_iter_meta *meta,
|
|
struct task_struct *task, struct vm_area_struct *vma)
|
|
|
|
static int __task_vma_seq_show(struct seq_file *seq, bool in_stop)
|
|
{
|
|
struct bpf_iter_seq_task_vma_info *info = seq->private;
|
|
struct bpf_iter__task_vma ctx;
|
|
struct bpf_iter_meta meta;
|
|
struct bpf_prog *prog;
|
|
|
|
meta.seq = seq;
|
|
prog = bpf_iter_get_info(&meta, in_stop);
|
|
if (!prog)
|
|
return 0;
|
|
|
|
ctx.meta = &meta;
|
|
ctx.task = info->task;
|
|
ctx.vma = info->vma;
|
|
return bpf_iter_run_prog(prog, &ctx);
|
|
}
|
|
|
|
static int task_vma_seq_show(struct seq_file *seq, void *v)
|
|
{
|
|
return __task_vma_seq_show(seq, false);
|
|
}
|
|
|
|
static void task_vma_seq_stop(struct seq_file *seq, void *v)
|
|
{
|
|
struct bpf_iter_seq_task_vma_info *info = seq->private;
|
|
|
|
if (!v) {
|
|
(void)__task_vma_seq_show(seq, true);
|
|
} else {
|
|
/* info->vma has not been seen by the BPF program. If the
|
|
* user space reads more, task_vma_seq_get_next should
|
|
* return this vma again. Set prev_vm_start to ~0UL,
|
|
* so that we don't skip the vma returned by the next
|
|
* find_vma() (case task_vma_iter_find_vma in
|
|
* task_vma_seq_get_next()).
|
|
*/
|
|
info->prev_vm_start = ~0UL;
|
|
info->prev_vm_end = info->vma->vm_end;
|
|
mmap_read_unlock(info->mm);
|
|
mmput(info->mm);
|
|
info->mm = NULL;
|
|
put_task_struct(info->task);
|
|
info->task = NULL;
|
|
}
|
|
}
|
|
|
|
static const struct seq_operations task_vma_seq_ops = {
|
|
.start = task_vma_seq_start,
|
|
.next = task_vma_seq_next,
|
|
.stop = task_vma_seq_stop,
|
|
.show = task_vma_seq_show,
|
|
};
|
|
|
|
static const struct bpf_iter_seq_info task_seq_info = {
|
|
.seq_ops = &task_seq_ops,
|
|
.init_seq_private = init_seq_pidns,
|
|
.fini_seq_private = fini_seq_pidns,
|
|
.seq_priv_size = sizeof(struct bpf_iter_seq_task_info),
|
|
};
|
|
|
|
static int bpf_iter_fill_link_info(const struct bpf_iter_aux_info *aux, struct bpf_link_info *info)
|
|
{
|
|
switch (aux->task.type) {
|
|
case BPF_TASK_ITER_TID:
|
|
info->iter.task.tid = aux->task.pid;
|
|
break;
|
|
case BPF_TASK_ITER_TGID:
|
|
info->iter.task.pid = aux->task.pid;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void bpf_iter_task_show_fdinfo(const struct bpf_iter_aux_info *aux, struct seq_file *seq)
|
|
{
|
|
seq_printf(seq, "task_type:\t%s\n", iter_task_type_names[aux->task.type]);
|
|
if (aux->task.type == BPF_TASK_ITER_TID)
|
|
seq_printf(seq, "tid:\t%u\n", aux->task.pid);
|
|
else if (aux->task.type == BPF_TASK_ITER_TGID)
|
|
seq_printf(seq, "pid:\t%u\n", aux->task.pid);
|
|
}
|
|
|
|
static struct bpf_iter_reg task_reg_info = {
|
|
.target = "task",
|
|
.attach_target = bpf_iter_attach_task,
|
|
.feature = BPF_ITER_RESCHED,
|
|
.ctx_arg_info_size = 1,
|
|
.ctx_arg_info = {
|
|
{ offsetof(struct bpf_iter__task, task),
|
|
PTR_TO_BTF_ID_OR_NULL | PTR_TRUSTED },
|
|
},
|
|
.seq_info = &task_seq_info,
|
|
.fill_link_info = bpf_iter_fill_link_info,
|
|
.show_fdinfo = bpf_iter_task_show_fdinfo,
|
|
};
|
|
|
|
static const struct bpf_iter_seq_info task_file_seq_info = {
|
|
.seq_ops = &task_file_seq_ops,
|
|
.init_seq_private = init_seq_pidns,
|
|
.fini_seq_private = fini_seq_pidns,
|
|
.seq_priv_size = sizeof(struct bpf_iter_seq_task_file_info),
|
|
};
|
|
|
|
static struct bpf_iter_reg task_file_reg_info = {
|
|
.target = "task_file",
|
|
.attach_target = bpf_iter_attach_task,
|
|
.feature = BPF_ITER_RESCHED,
|
|
.ctx_arg_info_size = 2,
|
|
.ctx_arg_info = {
|
|
{ offsetof(struct bpf_iter__task_file, task),
|
|
PTR_TO_BTF_ID_OR_NULL },
|
|
{ offsetof(struct bpf_iter__task_file, file),
|
|
PTR_TO_BTF_ID_OR_NULL },
|
|
},
|
|
.seq_info = &task_file_seq_info,
|
|
.fill_link_info = bpf_iter_fill_link_info,
|
|
.show_fdinfo = bpf_iter_task_show_fdinfo,
|
|
};
|
|
|
|
static const struct bpf_iter_seq_info task_vma_seq_info = {
|
|
.seq_ops = &task_vma_seq_ops,
|
|
.init_seq_private = init_seq_pidns,
|
|
.fini_seq_private = fini_seq_pidns,
|
|
.seq_priv_size = sizeof(struct bpf_iter_seq_task_vma_info),
|
|
};
|
|
|
|
static struct bpf_iter_reg task_vma_reg_info = {
|
|
.target = "task_vma",
|
|
.attach_target = bpf_iter_attach_task,
|
|
/*
|
|
* ->show() runs with the mmap_lock of the iterated mm held, which a
|
|
* sleepable program takes again when it faults on user memory.
|
|
*/
|
|
.feature = BPF_ITER_RESCHED | BPF_ITER_NOSLEEP,
|
|
.ctx_arg_info_size = 2,
|
|
.ctx_arg_info = {
|
|
{ offsetof(struct bpf_iter__task_vma, task),
|
|
PTR_TO_BTF_ID_OR_NULL },
|
|
{ offsetof(struct bpf_iter__task_vma, vma),
|
|
PTR_TO_BTF_ID_OR_NULL },
|
|
},
|
|
.seq_info = &task_vma_seq_info,
|
|
.fill_link_info = bpf_iter_fill_link_info,
|
|
.show_fdinfo = bpf_iter_task_show_fdinfo,
|
|
};
|
|
|
|
BPF_CALL_5(bpf_find_vma, struct task_struct *, task, u64, start,
|
|
bpf_callback_t, callback_fn, void *, callback_ctx, u64, flags)
|
|
{
|
|
struct mmap_unlock_irq_work *work;
|
|
struct vm_area_struct *vma;
|
|
bool __maybe_unused mmput_needed = false;
|
|
struct mm_struct *mm;
|
|
int ret = -ENOENT;
|
|
|
|
if (flags)
|
|
return -EINVAL;
|
|
|
|
if (!task)
|
|
return -ENOENT;
|
|
|
|
if (task == current) {
|
|
mm = task->mm;
|
|
} else {
|
|
/*
|
|
* Foreign task: pin task->mm against a concurrent exit_mm().
|
|
* Use trylock on alloc_lock instead of get_task_mm()'s
|
|
* blocking task_lock() to avoid deadlocking the target task.
|
|
*/
|
|
if (!IS_ENABLED(CONFIG_MMU))
|
|
return -EOPNOTSUPP;
|
|
if (irqs_disabled())
|
|
return -EBUSY;
|
|
if (!spin_trylock(&task->alloc_lock))
|
|
return -EBUSY;
|
|
mm = task->mm;
|
|
if (mm && !(task->flags & PF_KTHREAD)) {
|
|
mmget(mm);
|
|
mmput_needed = true;
|
|
} else {
|
|
mm = NULL;
|
|
}
|
|
spin_unlock(&task->alloc_lock);
|
|
}
|
|
if (!mm)
|
|
return -ENOENT;
|
|
|
|
work = bpf_mmap_unlock_guard_get();
|
|
if (IS_ERR(work)) {
|
|
ret = PTR_ERR(work);
|
|
goto out;
|
|
}
|
|
|
|
if (!mmap_read_trylock(mm)) {
|
|
bpf_mmap_unlock_guard_put(work);
|
|
ret = -EBUSY;
|
|
goto out;
|
|
}
|
|
|
|
vma = find_vma(mm, start);
|
|
|
|
if (vma && vma->vm_start <= start && vma->vm_end > start) {
|
|
/*
|
|
* The callback must not sleep while mmap_lock is held, and the
|
|
* verifier checks it as an RCU read-side critical section.
|
|
*/
|
|
rcu_read_lock();
|
|
callback_fn((u64)(long)task, (u64)(long)vma,
|
|
(u64)(long)callback_ctx, 0, 0);
|
|
rcu_read_unlock();
|
|
ret = 0;
|
|
}
|
|
bpf_mmap_unlock_mm(work, mm);
|
|
out:
|
|
#ifdef CONFIG_MMU
|
|
if (mmput_needed)
|
|
mmput_async(mm);
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
const struct bpf_func_proto bpf_find_vma_proto = {
|
|
.func = bpf_find_vma,
|
|
.ret_type = RET_INTEGER,
|
|
.arg1_type = ARG_PTR_TO_BTF_ID,
|
|
.arg1_btf_id = &btf_tracing_ids[BTF_TRACING_TYPE_TASK],
|
|
.arg2_type = ARG_ANYTHING,
|
|
.arg3_type = ARG_PTR_TO_FUNC,
|
|
.arg4_type = ARG_PTR_TO_STACK_OR_NULL,
|
|
.arg5_type = ARG_ANYTHING,
|
|
};
|
|
|
|
static inline void bpf_iter_mmput_async(struct mm_struct *mm)
|
|
{
|
|
#ifdef CONFIG_MMU
|
|
mmput_async(mm);
|
|
#else
|
|
mmput(mm);
|
|
#endif
|
|
}
|
|
|
|
struct bpf_iter_task_vma_kern_data {
|
|
struct task_struct *task;
|
|
struct mm_struct *mm;
|
|
struct vm_area_struct snapshot;
|
|
u64 next_addr;
|
|
};
|
|
|
|
struct bpf_iter_task_vma {
|
|
/* opaque iterator state; having __u64 here allows to preserve correct
|
|
* alignment requirements in vmlinux.h, generated from BTF
|
|
*/
|
|
__u64 __opaque[1];
|
|
} __attribute__((aligned(8)));
|
|
|
|
/* Non-opaque version of bpf_iter_task_vma */
|
|
struct bpf_iter_task_vma_kern {
|
|
struct bpf_iter_task_vma_kern_data *data;
|
|
} __attribute__((aligned(8)));
|
|
|
|
__bpf_kfunc_start_defs();
|
|
|
|
__bpf_kfunc int bpf_iter_task_vma_new(struct bpf_iter_task_vma *it,
|
|
struct task_struct *task, u64 addr)
|
|
{
|
|
struct bpf_iter_task_vma_kern *kit = (void *)it;
|
|
int err;
|
|
|
|
BUILD_BUG_ON(sizeof(struct bpf_iter_task_vma_kern) != sizeof(struct bpf_iter_task_vma));
|
|
BUILD_BUG_ON(__alignof__(struct bpf_iter_task_vma_kern) != __alignof__(struct bpf_iter_task_vma));
|
|
|
|
if (!IS_ENABLED(CONFIG_MMU)) {
|
|
kit->data = NULL;
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
/*
|
|
* Reject irqs-disabled contexts including NMI. Operations used
|
|
* by _next() and _destroy() (vma_end_read, fput, bpf_iter_mmput_async)
|
|
* can take spinlocks with IRQs disabled (pi_lock, pool->lock).
|
|
* Running from NMI or from a tracepoint that fires with those
|
|
* locks held could deadlock.
|
|
*/
|
|
if (irqs_disabled()) {
|
|
kit->data = NULL;
|
|
return -EBUSY;
|
|
}
|
|
|
|
/* is_iter_reg_valid_uninit guarantees that kit hasn't been initialized
|
|
* before, so non-NULL kit->data doesn't point to previously
|
|
* bpf_mem_alloc'd bpf_iter_task_vma_kern_data
|
|
*/
|
|
kit->data = bpf_mem_alloc(&bpf_global_ma, sizeof(struct bpf_iter_task_vma_kern_data));
|
|
if (!kit->data)
|
|
return -ENOMEM;
|
|
|
|
kit->data->task = get_task_struct(task);
|
|
/*
|
|
* Safely read task->mm and acquire an mm reference.
|
|
*
|
|
* Cannot use get_task_mm() because its task_lock() is a
|
|
* blocking spin_lock that would deadlock if the target task
|
|
* already holds alloc_lock on this CPU (e.g. a softirq BPF
|
|
* program iterating a task interrupted while holding its
|
|
* alloc_lock).
|
|
*/
|
|
if (!spin_trylock(&task->alloc_lock)) {
|
|
err = -EBUSY;
|
|
goto err_cleanup_iter;
|
|
}
|
|
kit->data->mm = task->mm;
|
|
if (kit->data->mm && !(task->flags & PF_KTHREAD))
|
|
mmget(kit->data->mm);
|
|
else
|
|
kit->data->mm = NULL;
|
|
spin_unlock(&task->alloc_lock);
|
|
if (!kit->data->mm) {
|
|
err = -ENOENT;
|
|
goto err_cleanup_iter;
|
|
}
|
|
|
|
kit->data->snapshot.vm_file = NULL;
|
|
kit->data->next_addr = addr;
|
|
return 0;
|
|
|
|
err_cleanup_iter:
|
|
put_task_struct(kit->data->task);
|
|
bpf_mem_free(&bpf_global_ma, kit->data);
|
|
/* NULL kit->data signals failed bpf_iter_task_vma initialization */
|
|
kit->data = NULL;
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* Find and lock the next VMA at or after data->next_addr.
|
|
*
|
|
* lock_vma_under_rcu() is a point lookup (mas_walk): it finds the VMA
|
|
* containing a given address but cannot iterate. An RCU-protected
|
|
* maple tree walk with vma_next() (mas_find) is needed first to locate
|
|
* the next VMA's vm_start across any gap.
|
|
*
|
|
* Between the RCU walk and the lock, the VMA may be removed, shrunk,
|
|
* or write-locked. On failure, advance past it using vm_end from the
|
|
* RCU walk. SLAB_TYPESAFE_BY_RCU can make vm_end stale, so fall back
|
|
* to PAGE_SIZE advancement to guarantee forward progress.
|
|
*/
|
|
static struct vm_area_struct *
|
|
bpf_iter_task_vma_find_next(struct bpf_iter_task_vma_kern_data *data)
|
|
{
|
|
struct vm_area_struct *vma;
|
|
struct vma_iterator vmi;
|
|
unsigned long start, end;
|
|
|
|
retry:
|
|
rcu_read_lock();
|
|
vma_iter_init(&vmi, data->mm, data->next_addr);
|
|
vma = vma_next(&vmi);
|
|
if (!vma) {
|
|
rcu_read_unlock();
|
|
return NULL;
|
|
}
|
|
start = vma->vm_start;
|
|
end = vma->vm_end;
|
|
rcu_read_unlock();
|
|
|
|
vma = lock_vma_under_rcu(data->mm, start);
|
|
if (!vma) {
|
|
if (end <= data->next_addr)
|
|
data->next_addr += PAGE_SIZE;
|
|
else
|
|
data->next_addr = end;
|
|
goto retry;
|
|
}
|
|
|
|
if (unlikely(vma->vm_end <= data->next_addr)) {
|
|
data->next_addr += PAGE_SIZE;
|
|
vma_end_read(vma);
|
|
goto retry;
|
|
}
|
|
|
|
return vma;
|
|
}
|
|
|
|
static void bpf_iter_task_vma_snapshot_reset(struct vm_area_struct *snap)
|
|
{
|
|
if (snap->vm_file) {
|
|
fput(snap->vm_file);
|
|
snap->vm_file = NULL;
|
|
}
|
|
}
|
|
|
|
__bpf_kfunc struct vm_area_struct *bpf_iter_task_vma_next(struct bpf_iter_task_vma *it)
|
|
{
|
|
struct bpf_iter_task_vma_kern *kit = (void *)it;
|
|
struct vm_area_struct *snap, *vma;
|
|
|
|
if (!kit->data) /* bpf_iter_task_vma_new failed */
|
|
return NULL;
|
|
|
|
snap = &kit->data->snapshot;
|
|
|
|
bpf_iter_task_vma_snapshot_reset(snap);
|
|
|
|
vma = bpf_iter_task_vma_find_next(kit->data);
|
|
if (!vma)
|
|
return NULL;
|
|
|
|
memcpy(snap, vma, sizeof(*snap));
|
|
|
|
/*
|
|
* The verifier only trusts vm_mm and vm_file (see
|
|
* BTF_TYPE_SAFE_TRUSTED_OR_NULL in verifier.c). Take a reference
|
|
* on vm_file; vm_mm is already correct because lock_vma_under_rcu()
|
|
* verifies vma->vm_mm == mm. All other pointers are untrusted by
|
|
* the verifier and left as-is.
|
|
*/
|
|
if (snap->vm_file)
|
|
get_file(snap->vm_file);
|
|
|
|
kit->data->next_addr = vma->vm_end;
|
|
vma_end_read(vma);
|
|
return snap;
|
|
}
|
|
|
|
__bpf_kfunc void bpf_iter_task_vma_destroy(struct bpf_iter_task_vma *it)
|
|
{
|
|
struct bpf_iter_task_vma_kern *kit = (void *)it;
|
|
|
|
if (kit->data) {
|
|
bpf_iter_task_vma_snapshot_reset(&kit->data->snapshot);
|
|
put_task_struct(kit->data->task);
|
|
bpf_iter_mmput_async(kit->data->mm);
|
|
bpf_mem_free(&bpf_global_ma, kit->data);
|
|
}
|
|
}
|
|
|
|
__bpf_kfunc_end_defs();
|
|
|
|
#ifdef CONFIG_CGROUPS
|
|
|
|
struct bpf_iter_css_task {
|
|
__u64 __opaque[1];
|
|
} __attribute__((aligned(8)));
|
|
|
|
struct bpf_iter_css_task_kern {
|
|
struct css_task_iter *css_it;
|
|
} __attribute__((aligned(8)));
|
|
|
|
__bpf_kfunc_start_defs();
|
|
|
|
__bpf_kfunc int bpf_iter_css_task_new(struct bpf_iter_css_task *it,
|
|
struct cgroup_subsys_state *css, unsigned int flags)
|
|
{
|
|
struct bpf_iter_css_task_kern *kit = (void *)it;
|
|
|
|
BUILD_BUG_ON(sizeof(struct bpf_iter_css_task_kern) != sizeof(struct bpf_iter_css_task));
|
|
BUILD_BUG_ON(__alignof__(struct bpf_iter_css_task_kern) !=
|
|
__alignof__(struct bpf_iter_css_task));
|
|
kit->css_it = NULL;
|
|
switch (flags) {
|
|
case CSS_TASK_ITER_PROCS | CSS_TASK_ITER_THREADED:
|
|
case CSS_TASK_ITER_PROCS:
|
|
case 0:
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
kit->css_it = bpf_mem_alloc(&bpf_global_ma, sizeof(struct css_task_iter));
|
|
if (!kit->css_it)
|
|
return -ENOMEM;
|
|
css_task_iter_start(css, flags, kit->css_it);
|
|
return 0;
|
|
}
|
|
|
|
__bpf_kfunc struct task_struct *bpf_iter_css_task_next(struct bpf_iter_css_task *it)
|
|
{
|
|
struct bpf_iter_css_task_kern *kit = (void *)it;
|
|
|
|
if (!kit->css_it)
|
|
return NULL;
|
|
return css_task_iter_next(kit->css_it);
|
|
}
|
|
|
|
__bpf_kfunc void bpf_iter_css_task_destroy(struct bpf_iter_css_task *it)
|
|
{
|
|
struct bpf_iter_css_task_kern *kit = (void *)it;
|
|
|
|
if (!kit->css_it)
|
|
return;
|
|
css_task_iter_end(kit->css_it);
|
|
bpf_mem_free(&bpf_global_ma, kit->css_it);
|
|
}
|
|
|
|
__bpf_kfunc_end_defs();
|
|
|
|
#endif /* CONFIG_CGROUPS */
|
|
|
|
struct bpf_iter_task {
|
|
__u64 __opaque[3];
|
|
} __attribute__((aligned(8)));
|
|
|
|
struct bpf_iter_task_kern {
|
|
struct task_struct *task;
|
|
struct task_struct *pos;
|
|
unsigned int flags;
|
|
} __attribute__((aligned(8)));
|
|
|
|
enum {
|
|
/* all process in the system */
|
|
BPF_TASK_ITER_ALL_PROCS,
|
|
/* all threads in the system */
|
|
BPF_TASK_ITER_ALL_THREADS,
|
|
/* all threads of a specific process */
|
|
BPF_TASK_ITER_PROC_THREADS
|
|
};
|
|
|
|
__bpf_kfunc_start_defs();
|
|
|
|
__bpf_kfunc int bpf_iter_task_new(struct bpf_iter_task *it,
|
|
struct task_struct *task__nullable, unsigned int flags)
|
|
{
|
|
struct bpf_iter_task_kern *kit = (void *)it;
|
|
|
|
BUILD_BUG_ON(sizeof(struct bpf_iter_task_kern) > sizeof(struct bpf_iter_task));
|
|
BUILD_BUG_ON(__alignof__(struct bpf_iter_task_kern) !=
|
|
__alignof__(struct bpf_iter_task));
|
|
|
|
kit->pos = NULL;
|
|
|
|
switch (flags) {
|
|
case BPF_TASK_ITER_ALL_THREADS:
|
|
case BPF_TASK_ITER_ALL_PROCS:
|
|
break;
|
|
case BPF_TASK_ITER_PROC_THREADS:
|
|
if (!task__nullable)
|
|
return -EINVAL;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (flags == BPF_TASK_ITER_PROC_THREADS)
|
|
kit->task = task__nullable;
|
|
else
|
|
kit->task = &init_task;
|
|
kit->pos = kit->task;
|
|
kit->flags = flags;
|
|
return 0;
|
|
}
|
|
|
|
__bpf_kfunc struct task_struct *bpf_iter_task_next(struct bpf_iter_task *it)
|
|
{
|
|
struct bpf_iter_task_kern *kit = (void *)it;
|
|
struct task_struct *pos;
|
|
unsigned int flags;
|
|
|
|
flags = kit->flags;
|
|
pos = kit->pos;
|
|
|
|
if (!pos)
|
|
return pos;
|
|
|
|
if (flags == BPF_TASK_ITER_ALL_PROCS)
|
|
goto get_next_task;
|
|
|
|
kit->pos = __next_thread(kit->pos);
|
|
if (kit->pos || flags == BPF_TASK_ITER_PROC_THREADS)
|
|
return pos;
|
|
|
|
get_next_task:
|
|
kit->task = next_task(kit->task);
|
|
if (kit->task == &init_task)
|
|
kit->pos = NULL;
|
|
else
|
|
kit->pos = kit->task;
|
|
|
|
return pos;
|
|
}
|
|
|
|
__bpf_kfunc void bpf_iter_task_destroy(struct bpf_iter_task *it)
|
|
{
|
|
}
|
|
|
|
__bpf_kfunc_end_defs();
|
|
|
|
DEFINE_PER_CPU(struct mmap_unlock_irq_work, mmap_unlock_work);
|
|
|
|
static void do_mmap_read_unlock(struct irq_work *entry)
|
|
{
|
|
struct mmap_unlock_irq_work *work;
|
|
|
|
if (WARN_ON_ONCE(IS_ENABLED(CONFIG_PREEMPT_RT)))
|
|
return;
|
|
|
|
work = container_of(entry, struct mmap_unlock_irq_work, irq_work);
|
|
mmap_read_unlock_non_owner(work->mm);
|
|
work->mm = NULL;
|
|
bpf_mmap_unlock_guard_put(work);
|
|
}
|
|
|
|
static int __init task_iter_init(void)
|
|
{
|
|
struct mmap_unlock_irq_work *work;
|
|
int ret, cpu;
|
|
|
|
for_each_possible_cpu(cpu) {
|
|
work = per_cpu_ptr(&mmap_unlock_work, cpu);
|
|
init_irq_work(&work->irq_work, do_mmap_read_unlock);
|
|
}
|
|
|
|
task_reg_info.ctx_arg_info[0].btf_id = btf_tracing_ids[BTF_TRACING_TYPE_TASK];
|
|
ret = bpf_iter_reg_target(&task_reg_info);
|
|
if (ret)
|
|
return ret;
|
|
|
|
task_file_reg_info.ctx_arg_info[0].btf_id = btf_tracing_ids[BTF_TRACING_TYPE_TASK];
|
|
task_file_reg_info.ctx_arg_info[1].btf_id = btf_tracing_ids[BTF_TRACING_TYPE_FILE];
|
|
ret = bpf_iter_reg_target(&task_file_reg_info);
|
|
if (ret)
|
|
return ret;
|
|
|
|
task_vma_reg_info.ctx_arg_info[0].btf_id = btf_tracing_ids[BTF_TRACING_TYPE_TASK];
|
|
task_vma_reg_info.ctx_arg_info[1].btf_id = btf_tracing_ids[BTF_TRACING_TYPE_VMA];
|
|
return bpf_iter_reg_target(&task_vma_reg_info);
|
|
}
|
|
late_initcall(task_iter_init);
|