| // 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/fdtable.h> |
| #include <linux/filter.h> |
| #include <linux/btf_ids.h> |
| |
| struct bpf_iter_seq_task_common { |
| struct pid_namespace *ns; |
| }; |
| |
| 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_seq_get_next(struct pid_namespace *ns, |
| u32 *tid, |
| bool skip_if_dup_files) |
| { |
| struct task_struct *task = NULL; |
| struct pid *pid; |
| |
| rcu_read_lock(); |
| retry: |
| pid = find_ge_pid(*tid, ns); |
| if (pid) { |
| *tid = pid_nr_ns(pid, ns); |
| task = get_pid_task(pid, PIDTYPE_PID); |
| if (!task) { |
| ++*tid; |
| goto retry; |
| } else if (skip_if_dup_files && task->tgid != task->pid && |
| 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.ns, &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.ns, &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; |
| |
| meta.seq = seq; |
| 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 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; |
| struct files_struct *files; |
| u32 tid; |
| u32 fd; |
| }; |
| |
| static struct file * |
| task_file_seq_get_next(struct bpf_iter_seq_task_file_info *info) |
| { |
| struct pid_namespace *ns = info->common.ns; |
| u32 curr_tid = info->tid, max_fds; |
| struct files_struct *curr_files; |
| struct task_struct *curr_task; |
| int curr_fd = info->fd; |
| |
| /* If this function returns a non-NULL file object, |
| * it held a reference to the task/files_struct/file. |
| * Otherwise, it does not hold any reference. |
| */ |
| again: |
| if (info->task) { |
| curr_task = info->task; |
| curr_files = info->files; |
| curr_fd = info->fd; |
| } else { |
| curr_task = task_seq_get_next(ns, &curr_tid, true); |
| if (!curr_task) { |
| info->task = NULL; |
| info->files = NULL; |
| info->tid = curr_tid; |
| return NULL; |
| } |
| |
| curr_files = get_files_struct(curr_task); |
| if (!curr_files) { |
| put_task_struct(curr_task); |
| curr_tid = curr_tid + 1; |
| info->fd = 0; |
| goto again; |
| } |
| |
| info->files = curr_files; |
| info->task = curr_task; |
| if (curr_tid == info->tid) { |
| curr_fd = info->fd; |
| } else { |
| info->tid = curr_tid; |
| curr_fd = 0; |
| } |
| } |
| |
| rcu_read_lock(); |
| max_fds = files_fdtable(curr_files)->max_fds; |
| for (; curr_fd < max_fds; curr_fd++) { |
| struct file *f; |
| |
| f = fcheck_files(curr_files, curr_fd); |
| if (!f) |
| continue; |
| if (!get_file_rcu(f)) |
| continue; |
| |
| /* set info->fd */ |
| info->fd = curr_fd; |
| rcu_read_unlock(); |
| return f; |
| } |
| |
| /* the current task is done, go to the next task */ |
| rcu_read_unlock(); |
| put_files_struct(curr_files); |
| put_task_struct(curr_task); |
| info->task = NULL; |
| info->files = NULL; |
| info->fd = 0; |
| curr_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; |
| info->files = 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_files_struct(info->files); |
| put_task_struct(info->task); |
| info->files = NULL; |
| 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)); |
| 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, |
| }; |
| |
| BTF_ID_LIST(btf_task_file_ids) |
| BTF_ID(struct, task_struct) |
| BTF_ID(struct, file) |
| |
| 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 struct bpf_iter_reg task_reg_info = { |
| .target = "task", |
| .ctx_arg_info_size = 1, |
| .ctx_arg_info = { |
| { offsetof(struct bpf_iter__task, task), |
| PTR_TO_BTF_ID_OR_NULL }, |
| }, |
| .seq_info = &task_seq_info, |
| }; |
| |
| 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", |
| .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, |
| }; |
| |
| static int __init task_iter_init(void) |
| { |
| int ret; |
| |
| task_reg_info.ctx_arg_info[0].btf_id = btf_task_file_ids[0]; |
| ret = bpf_iter_reg_target(&task_reg_info); |
| if (ret) |
| return ret; |
| |
| task_file_reg_info.ctx_arg_info[0].btf_id = btf_task_file_ids[0]; |
| task_file_reg_info.ctx_arg_info[1].btf_id = btf_task_file_ids[1]; |
| return bpf_iter_reg_target(&task_file_reg_info); |
| } |
| late_initcall(task_iter_init); |