[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <CAEf4Bzbbmfwd1m4F6iz_P2o5qGShd2orTF_vJBK_d-aSUXqkqw@mail.gmail.com>
Date: Tue, 5 Nov 2019 11:07:13 -0800
From: Andrii Nakryiko <andrii.nakryiko@...il.com>
To: Wenbo Zhang <ethercflow@...il.com>
Cc: bpf <bpf@...r.kernel.org>, Yonghong Song <yhs@...com>,
Daniel Borkmann <daniel@...earbox.net>,
Networking <netdev@...r.kernel.org>
Subject: Re: [PATCH bpf-next v3] selftests/bpf: test for bpf_get_file_path()
from raw tracepoint
On Mon, Nov 4, 2019 at 8:15 PM Wenbo Zhang <ethercflow@...il.com> wrote:
>
> trace fstat events by raw tracepoint sys_enter:newfstat, and handle events
> only produced by test_file_get_path, which call fstat on several different
> types of files to test bpf_get_file_path's feature.
>
> v2->v3: addressed Andrii's feedback
> - use global data instead of perf_buffer to simplified code
>
> v1->v2: addressed Daniel's feedback
> - rename bpf_fd2path to bpf_get_file_path to be consistent with other
> helper's names
>
> Signed-off-by: Wenbo Zhang <ethercflow@...il.com>
> ---
> .../selftests/bpf/prog_tests/get_file_path.c | 171 ++++++++++++++++++
> .../selftests/bpf/progs/test_get_file_path.c | 71 ++++++++
> 2 files changed, 242 insertions(+)
> create mode 100644 tools/testing/selftests/bpf/prog_tests/get_file_path.c
> create mode 100644 tools/testing/selftests/bpf/progs/test_get_file_path.c
>
> diff --git a/tools/testing/selftests/bpf/prog_tests/get_file_path.c b/tools/testing/selftests/bpf/prog_tests/get_file_path.c
> new file mode 100644
> index 000000000000..26126e55c1f0
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/get_file_path.c
> @@ -0,0 +1,171 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#define _GNU_SOURCE
> +#include <test_progs.h>
> +#include <alloca.h>
> +#include <sys/stat.h>
> +
> +#ifndef MAX_PATH_LENGTH
> +#define MAX_PATH_LENGTH 128
> +#endif
> +
> +#ifndef TASK_COMM_LEN
> +#define TASK_COMM_LEN 16
> +#endif
Do you really need these ifndefs? Either include headers that have
TASK_COMM_LEN, or don't and just define them directly?
> +
> +struct get_path_trace_t {
> + unsigned long fd;
> + char path[MAX_PATH_LENGTH];
> +};
> +
> +enum FS_TYPE {
> + PIPE_0,
> + PIPE_1,
> + SOCK,
> + PROC,
> + DEV,
> + LOCAL,
> + INDICATOR,
> + MAX_FDS
> +};
> +
> +struct path_info {
> + int fd;
> + char name[MAX_PATH_LENGTH];
> +};
> +
> +static struct path_info path_infos[MAX_FDS];
> +static int path_info_index;
> +static int hits;
> +
> +static inline int set_pathname(pid_t pid, int fd)
> +{
> + char buf[MAX_PATH_LENGTH] = {'0'};
This is not a zero byte, it's a character "0", was this intentional?
> +
> + snprintf(buf, MAX_PATH_LENGTH, "/proc/%d/fd/%d", pid, fd);
> + path_infos[path_info_index].fd = fd;
you can just pass path_info_index directly, there is absolutely no
need for global counter for this...
> + return readlink(buf, path_infos[path_info_index++].name,
> + MAX_PATH_LENGTH);
> +}
> +
> +static inline int compare_pathname(struct get_path_trace_t *data)
> +{
> + for (int i = 0; i < MAX_FDS; i++) {
> + if (path_infos[i].fd == data->fd) {
> + hits++;
> + return strncmp(path_infos[i].name, data->path,
> + MAX_PATH_LENGTH);
> + }
> + }
> + return 0;
> +}
> +
> +static int trigger_fstat_events(void)
> +{
> + int *fds = alloca(sizeof(int) * MAX_FDS);
why do you need alloca()? Doesn't int fds[MAX_FDS] work? But honestly,
you needs fds just to have a loop to close all FDs. You can just as
well have a set of directl close(pipefd); close(sockfd); and so on,
with same amount of code, but more simplicity.
> + int *pipefd = fds;
> + int *sockfd = fds + SOCK;
> + int *procfd = fds + PROC;
> + int *devfd = fds + DEV;
> + int *localfd = fds + LOCAL;
> + int *indicatorfd = fds + INDICATOR;
> + pid_t pid = getpid();
> +
> + /* unmountable pseudo-filesystems */
> + if (pipe(pipefd) < 0 || set_pathname(pid, *pipefd++) < 0 ||
> + set_pathname(pid, *pipefd) < 0)
> + return -1;
> +
> + /* unmountable pseudo-filesystems */
> + *sockfd = socket(AF_INET, SOCK_STREAM, 0);
> + if (*sockfd < 0 || set_pathname(pid, *sockfd) < 0)
> + return -1;
> +
> + /* mountable pseudo-filesystems */
> + *procfd = open("/proc/self/comm", O_RDONLY);
> + if (*procfd < 0 || set_pathname(pid, *procfd) < 0)
> + return -1;
> +
> + *devfd = open("/dev/urandom", O_RDONLY);
> + if (*devfd < 0 || set_pathname(pid, *devfd) < 0)
> + return -1;
> +
> + *localfd = open("/tmp/fd2path_loadgen.txt", O_CREAT|O_RDONLY);
> + if (*localfd < 0 || set_pathname(pid, *localfd) < 0)
> + return -1;
> +
> + *indicatorfd = open("/tmp/", O_PATH);
> + if (*indicatorfd < 0 || set_pathname(pid, *indicatorfd) < 0)
> + return -1;
on error, you are not closing any file descriptor
> +
> + for (int i = 0; i < MAX_FDS; i++)
> + close(fds[i]);
> +
> + remove("/tmp/fd2path_loadgen.txt");
> + return 0;
> +}
> +
> +void test_get_file_path(void)
> +{
> + const char *prog_name = "raw_tracepoint/sys_enter:newfstat";
> + const char *file = "./test_get_file_path.o";
> + int pidfilter_map_fd, pathdata_map_fd;
> + __u32 key, previous_key, duration = 0;
> + struct get_path_trace_t val = {};
> + struct bpf_program *prog = NULL;
> + struct bpf_object *obj = NULL;
> + struct bpf_link *link = NULL;
> + __u32 pid = getpid();
> + int err, prog_fd;
> +
> + err = bpf_prog_load(file, BPF_PROG_TYPE_RAW_TRACEPOINT, &obj, &prog_fd);
> + if (CHECK(err, "prog_load", "err %d errno %d\n", err, errno))
> + return;
> +
> + prog = bpf_object__find_program_by_title(obj, prog_name);
> + if (CHECK(!prog, "find_prog", "prog %s not found\n", prog_name))
> + goto out_close;
> +
> + link = bpf_program__attach_raw_tracepoint(prog, "sys_enter");
> + if (CHECK(IS_ERR(link), "attach_tp", "err %ld\n", PTR_ERR(link)))
> + goto out_close;
> +
> + pidfilter_map_fd = bpf_find_map(__func__, obj, "pidfilter_map");
> + if (CHECK(pidfilter_map_fd < 0, "bpf_find_map pidfilter_map",
> + "err: %s\n", strerror(errno)))
> + goto out_detach;
> +
> + err = bpf_map_update_elem(pidfilter_map_fd, &key, &pid, 0);
> + if (CHECK(err, "pidfilter_map update_elem", "err: %s\n",
> + strerror(errno)))
> + goto out_detach;
> +
> + err = trigger_fstat_events();
> + if (CHECK(err, "trigger_fstat_events", "open fd failed: %s\n",
> + strerror(errno)))
> + goto out_detach;
> +
> + pathdata_map_fd = bpf_find_map(__func__, obj, "pathdata_map");
> + if (CHECK_FAIL(pathdata_map_fd < 0))
> + goto out_detach;
> +
> + do {
> + err = bpf_map_lookup_elem(pathdata_map_fd, &key, &val);
> + if (CHECK(err, "lookup_elem from pathdata_map",
> + "err %s\n", strerror(errno)))
> + goto out_detach;
> +
> + CHECK(compare_pathname(&val) != 0,
> + "get_file_path", "failed to get path: %lu->%s\n",
> + val.fd, val.path);
Given you control the order of open()'s, you should know the order of
captured paths, so there is no need to find it or do hits++ logic.
Just check it positionally. See also below about global data usage.
> +
> + previous_key = key;
> + } while (bpf_map_get_next_key(pathdata_map_fd,
> + &previous_key, &key) == 0);
> +
> + CHECK(hits != MAX_FDS, "Lost event?", "%d != %d\n", hits, MAX_FDS);
> +
> +out_detach:
> + bpf_link__destroy(link);
> +out_close:
> + bpf_object__close(obj);
> +}
> diff --git a/tools/testing/selftests/bpf/progs/test_get_file_path.c b/tools/testing/selftests/bpf/progs/test_get_file_path.c
> new file mode 100644
> index 000000000000..10ec9a70c81c
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/test_get_file_path.c
> @@ -0,0 +1,71 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#include <linux/bpf.h>
> +#include <linux/ptrace.h>
> +#include <linux/sched.h>
> +#include <stdbool.h>
> +#include <string.h>
> +#include "bpf_helpers.h"
> +
> +#ifndef MAX_PATH_LENGTH
> +#define MAX_PATH_LENGTH 128
> +#endif
> +
> +#ifndef MAX_EVENT_NUM
> +#define MAX_EVENT_NUM 32
> +#endif
> +
> +struct path_trace_t {
> + unsigned long fd;
> + char path[MAX_PATH_LENGTH];
> +};
> +
> +struct {
> + __uint(type, BPF_MAP_TYPE_ARRAY);
> + __uint(max_entries, MAX_EVENT_NUM);
> + __type(key, __u32);
> + __type(value, struct path_trace_t);
> +} pathdata_map SEC(".maps");
> +
> +struct {
> + __uint(type, BPF_MAP_TYPE_ARRAY);
> + __uint(max_entries, 1);
> + __type(key, __u32);
> + __type(value, __u32);
> +} pidfilter_map SEC(".maps");
> +
> +struct {
> + __uint(type, BPF_MAP_TYPE_ARRAY);
> + __uint(max_entries, 1);
> + __type(key, __u32);
> + __type(value, __u32);
> +} index_map SEC(".maps");
This is not global variables, it's just maps. What I had in mind (and
what would still simplify even userspace part of tests, IMO) is
something like this:
struct file_path_test_data {
pid_t pid;
int cnt;
unsigned long fds[MAX_EVENT_NUM];
char paths[MAX_EVENT_NUM][MAX_PATH_LENGTH];
} data;
> +
> +SEC("raw_tracepoint/sys_enter:newfstat")
> +int bpf_prog(struct bpf_raw_tracepoint_args *ctx)
> +{
> + struct path_trace_t *data;
> + struct pt_regs *regs;
> + __u32 key = 0, *i, *pidfilter, pid;
> +
> + pidfilter = bpf_map_lookup_elem(&pidfilter_map, &key);
> + if (!pidfilter || *pidfilter == 0)
> + return 0;
> + i = bpf_map_lookup_elem(&index_map, &key);
> + if (!i || *i == MAX_EVENT_NUM)
> + return 0;
> + pid = bpf_get_current_pid_tgid() >> 32;
> + if (pid != *pidfilter)
> + return 0;
> + data = bpf_map_lookup_elem(&pathdata_map, i);
> + if (!data)
> + return 0;
here, you'll do:
if (pid != data.pid) return 0;
data.cnt++;
if (data.cnt > MAX_EVENT_NUM) return 0; /* check overflow in userspace */
data.fds[data.cnt - 1] = /* read fd */
bpf_get_file_path(data.paths[data.cnt - 1], ...)
> +
> + regs = (struct pt_regs *)ctx->args[0];
> + bpf_probe_read(&data->fd, sizeof(data->fd), ®s->rdi);
This is x86_64-specific, use one of PT_REGS_* macro from bpf_tracing.h header.
> + bpf_get_file_path(data->path, MAX_PATH_LENGTH, data->fd);
> + *i += 1;
> +
> + return 0;
> +}
> +
> +char _license[] SEC("license") = "GPL";
> --
> 2.17.1
>
Powered by blists - more mailing lists