From 063b9cb305be439cd57ee5c7d652ac49d8340425 Mon Sep 17 00:00:00 2001
From: Luis Gerhorst <privat@luisgerhorst.de>
Date: Wed, 7 Jul 2021 14:44:33 +0200
Subject: [PATCH] task_find: Print pathnames from user pointer

---
 libbpf              |   2 +-
 src/task_find.bpf.c | 151 +++++++++++++++++++++++++++++---------------
 src/task_find.c     |  22 +++++--
 src/task_find.h     |   8 ++-
 src/task_lib.bpf.h  |  31 +++++++++
 5 files changed, 154 insertions(+), 60 deletions(-)

diff --git a/libbpf b/libbpf
index e6fdf39..8f83692 160000
--- a/libbpf
+++ b/libbpf
@@ -1 +1 @@
-Subproject commit e6fdf39f4c704221159e9f45f27a3ec6d56cfd4b
+Subproject commit 8f8369223c327743277a8e4fcbf87c5d9d6f3c88
diff --git a/src/task_find.bpf.c b/src/task_find.bpf.c
index a0f8244..5f84a91 100644
--- a/src/task_find.bpf.c
+++ b/src/task_find.bpf.c
@@ -10,49 +10,116 @@ char LICENSE[] SEC("license") = "Dual BSD/GPL";
 
 struct {
 	__uint(type, BPF_MAP_TYPE_ARRAY);
-	__uint(max_entries, 1);
+	__uint(max_entries, NR_STATE_MAP_KEYS);
 	__type(key, int);
 	__type(value, int);
-} pdpf_map SEC(".maps");
+} state_map SEC(".maps");
 
-uintptr_t root_path_user;
+struct {
+	__uint(type, BPF_MAP_TYPE_ARRAY);
+	__uint(max_entries, MAX_DEPTH);
+	__type(key, int);
+	__type(value, int);
+} dfd_map SEC(".maps");
 
-SEC("task")
-int entry(void *ctx)
-{
-	int err = 0;
+void *u_descend;
+void *u_ascend;
+void *u_newline;
+void *u_iterdents64_buf;
+size_t root_namelen;
 
-	char root_path[PATHLEN];
-	err = bpf_probe_read_user(root_path, PATHLEN, root_path_user);
-	if (err) {
-		bpf_printk("Error: read_user = %d", err);
+static long iter_dir(size_t namelen) {
+	long err = 0;
+
+	if (namelen + 1 > PATHLEN) {
+		bpf_printk("Error: too long name");
+		err = -1;
+		goto end;
+	}
+
+	int *prog_fd = bpf_map_lookup_elem(&state_map, &PDPF_KEY);
+	if (!prog_fd) {
+		bpf_printk("Error: !bpf_map_lookup_elem(&state_map, &PDPF_KEY)");
+		goto end;
+	}
+
+	int *curr_depth_p = bpf_map_lookup_elem(&state_map, &CURR_DEPTH_KEY);
+	if (!curr_depth_p) {
+		bpf_printk("Error: bpf_map_lookup_elem(&state_map, &CURR_DEPTH_KEY)");
+		goto end;
+	}
+	int depth = *curr_depth_p;
+	if (depth >= MAX_DEPTH) {
+		bpf_printk("Error: max_depth exceeded");
+		err = -1;
+		goto end;
+	}
+
+	void *name_user = u_iterdents64_buf + depth * PATHLEN;
+
+	int *parent_dfd = bpf_map_lookup_elem(&dfd_map, &depth);
+	if (!parent_dfd) {
+		bpf_printk("Error: bpf_map_lookup_elem(&dfd_map, depth)");
 		goto end;
 	}
-	bpf_printk("root_path = %s", root_path);
 
-	int dfd = bpf_task_sys_open_3(root_path_user, O_RDONLY | O_DIRECTORY, 0777);
+	int dfd = openat(*parent_dfd, name_user, O_RDONLY | O_DIRECTORY, 0777);
 	if (dfd < 0) {
-		err = dfd;
-		bpf_printk("Error: sys_open = %d", dfd);
+		bpf_printk("file, depth = %d", depth);
+		write(STDOUT_FILENO, name_user, namelen);
+		write(STDOUT_FILENO, u_newline, 1);
+		/* Not a directory, no need to iterate. */
 		goto end;
 	}
 
-	int *prog_fd = bpf_map_lookup_elem(&pdpf_map, &PDPF_KEY);
-	if (!prog_fd) {
-		bpf_printk("Error: !bpf_map_lookup_elem(&pdpf_map, &PDPF_KEY)");
+	bpf_printk("descend dir %d, depth = %d", dfd, depth);
+	write(STDOUT_FILENO, name_user, namelen);
+	write(STDOUT_FILENO, u_descend, 2);
+
+	int child_depth = depth + 1;
+	void *child_name_user = u_iterdents64_buf + child_depth * PATHLEN;
+
+	err = bpf_map_update_elem(&state_map, &CURR_DEPTH_KEY, &child_depth, BPF_ANY);
+	if (err) {
+		bpf_printk("Error: bpf_map_update_elem(&state_map, &curr_depth_key, &child_depth, bpf_exist)");
 		goto close;
 	}
 
-	err = bpf_task_iterdents64(dfd, *prog_fd);
+	err = bpf_map_update_elem(&dfd_map, &child_depth, &dfd, BPF_ANY);
 	if (err) {
-		bpf_printk("iterdents64(%d, %d) = %d", dfd, *prog_fd, err);
-		goto close;
+		bpf_printk("Error: bpf_map_update_elem(&dfd_map, &child_depth, &dfd, BPF_ANY)");
+		goto reset_depth;
 	}
 
+	err = bpf_task_iterdents64(dfd, *prog_fd, child_name_user, PATHLEN);
+	if (err) {
+		bpf_printk("Error: iterdents64(%d, %d) = %d", dfd, *prog_fd, err);
+		goto reset_depth;
+	}
+
+	bpf_printk("ascend dir %d, depth = %d done", dfd, depth);
+reset_depth:
+	err = bpf_map_update_elem(&state_map, &CURR_DEPTH_KEY, &depth, BPF_ANY);
+	if (err) {
+		bpf_printk("bpf_map_update_elem(&state_map, &CURR_DEPTH_KEY, depth, BPF_ANY) = %d", err);
+	}
 close:
 	err = bpf_task_sys_close_1(dfd);
 	if (err) {
-		bpf_printk("close_err = %d", err);
+		bpf_printk("close(%d) = %d", dfd, err);
+	}
+	write(STDOUT_FILENO, u_ascend, 3);
+end:
+	return err;
+}
+
+SEC("task")
+int entry(void *ctx)
+{
+	long err = 0;
+
+	err = iter_dir(root_namelen);
+	if (err) {
 		goto end;
 	}
 end:
@@ -64,43 +131,23 @@ int process_dirent(struct bpf_dirent64 *d) {
 	int err = 0;
 
 	/* Skip "." and "..": */
-	/* TODO: Why doesn't the verifier get this? */
-	if (d->name + 2 > d->name_end) {
-		goto end;
-	}
-	if (d->name[0] == '.' && d->namelen == 1) {
-		goto end;
+	if (!(d->name + 2 > (char *) d->name_end)) {
+		if (d->name[0] == '.' && d->namelen == 1) {
+			goto end;
+		}
 	}
 
-	if (d->name + 3 > d->name_end) {
-		goto end;
-	}
-	if (d->name[0] == '.' && d->name[1] == '.' && d->namelen == 2) {
-		goto end;
-	}
-
-	int dfd = bpf_task_openat(d, O_RDONLY | O_DIRECTORY, 0777);
-	if (dfd < 0) {
-		goto end;
+	if (!(d->name + 3 > (char *) d->name_end)) {
+		if (d->name[0] == '.' && d->name[1] == '.' && d->namelen == 2) {
+			goto end;
+		}
 	}
 
-	int *prog_fd = bpf_map_lookup_elem(&pdpf_map, &PDPF_KEY);
-	if (!prog_fd) {
-		bpf_printk("Error: !bpf_map_lookup_elem(&pdpf_map, &PDPF_KEY)");
-		goto close;
-	}
-
-	err = bpf_task_iterdents64(dfd, *prog_fd);
+	err = iter_dir(d->namelen);
 	if (err) {
-		bpf_printk("iterdents64(%d, %d) = %d", dfd, *prog_fd, err);
-		goto close;
+		goto end;
 	}
 
-close:
-	err = bpf_task_sys_close_1(dfd);
-	if (err) {
-		bpf_printk("close_err = %d", err);
-	}
 end:
 	return err;
 }
diff --git a/src/task_find.c b/src/task_find.c
index 21512fc..cd44ea4 100644
--- a/src/task_find.c
+++ b/src/task_find.c
@@ -167,9 +167,15 @@ static int bpf_find_by_name(const char *path, const char *name, bool debug) {
 		return EXIT_FAILURE;
 	}
 
-	char root_path_user[PATHLEN];
-	strncpy(root_path_user, path, PATHLEN);
-	skel->bss->root_path_user = root_path_user;
+	skel->bss->u_newline = "\n";
+	skel->bss->u_descend = "/\n";
+	skel->bss->u_ascend = "..\n";
+
+	char iterdents64_buf[MAX_DEPTH * PATHLEN];
+	skel->bss->u_iterdents64_buf = iterdents64_buf;
+	strncpy(iterdents64_buf, path, PATHLEN);
+
+	skel->bss->root_namelen = strlen(path);
 
 	/* Load & verify BPF programs */
 	err = BPF_LOAD(skel);
@@ -181,9 +187,15 @@ static int bpf_find_by_name(const char *path, const char *name, bool debug) {
 
 	/* BPF_NOEXIST means: add new element if it doesn't exist */
 	int value = bpf_program__fd(skel->progs.process_dirent);
-	err = bpf_map_update_elem(bpf_map__fd(skel->maps.pdpf_map), &PDPF_KEY, &value, BPF_ANY);
+	err = bpf_map_update_elem(bpf_map__fd(skel->maps.state_map), &PDPF_KEY, &value, BPF_ANY);
+	if (err == -1) {
+		perror("bpf_map_update_elem(bpf_map__fd(skel->maps.state_map), &PDPF_KEY, &value, BPF_NOEXIST)");
+		goto destroy;
+	}
+
+	int root_key = 0, root_value = AT_FDCWD;
+	err = bpf_map_update_elem(bpf_map__fd(skel->maps.dfd_map), &root_key, &root_value, BPF_ANY);
 	if (err == -1) {
-		perror("bpf_map_update_elem(bpf_map__fd(skel->maps.pdpf_map), &PDPF_KEY, &value, BPF_NOEXIST)");
 		goto destroy;
 	}
 
diff --git a/src/task_find.h b/src/task_find.h
index dd9954f..5703074 100644
--- a/src/task_find.h
+++ b/src/task_find.h
@@ -1,15 +1,19 @@
 #ifndef __TASK_H_
 #define __TASK_H_
 
-#define MAX_DEPTH 16
+#define MAX_DEPTH 32
 #define UBUF_SIZE 1024
-#define PATHLEN 256
+#define PATHLEN 512
 
 #define __packed __attribute__((__packed__))
 
 struct __packed __attribute__((__aligned__(4096))) bpfmem {
 };
 
+/* Can not be an enum because we have to pass pointers to the values to
+ * bpf_map_*() helpers. */
 static int PDPF_KEY = 0;
+static int CURR_DEPTH_KEY = 1;
+#define NR_STATE_MAP_KEYS 2
 
 #endif // __TASK_H_
diff --git a/src/task_lib.bpf.h b/src/task_lib.bpf.h
index d39a226..e4e1784 100644
--- a/src/task_lib.bpf.h
+++ b/src/task_lib.bpf.h
@@ -30,6 +30,33 @@ typedef off64_t off_t;
 #define STDOUT_FILENO 1
 #define STDERR_FILENO 2
 
+/*
+ * The constants AT_REMOVEDIR and AT_EACCESS have the same value.  AT_EACCESS is
+ * meaningful only to faccessat, while AT_REMOVEDIR is meaningful only to
+ * unlinkat.  The two functions do completely different things and therefore,
+ * the flags can be allowed to overlap.  For example, passing AT_REMOVEDIR to
+ * faccessat would be undefined behavior and thus treating it equivalent to
+ * AT_EACCESS is valid undefined behavior.
+ */
+#define AT_FDCWD		-100    /* Special value used to indicate
+                                           openat should use the current
+                                           working directory. */
+#define AT_SYMLINK_NOFOLLOW	0x100   /* Do not follow symbolic links.  */
+#define AT_EACCESS		0x200	/* Test access permitted for
+                                           effective IDs, not real IDs.  */
+#define AT_REMOVEDIR		0x200   /* Remove directory instead of
+                                           unlinking file.  */
+#define AT_SYMLINK_FOLLOW	0x400   /* Follow symbolic links.  */
+#define AT_NO_AUTOMOUNT		0x800	/* Suppress terminal automount traversal */
+#define AT_EMPTY_PATH		0x1000	/* Allow empty relative pathname */
+
+#define AT_STATX_SYNC_TYPE	0x6000	/* Type of synchronisation required from statx() */
+#define AT_STATX_SYNC_AS_STAT	0x0000	/* - Do whatever stat() does */
+#define AT_STATX_FORCE_SYNC	0x2000	/* - Force the attributes to be sync'd with the server */
+#define AT_STATX_DONT_SYNC	0x4000	/* - Don't sync attributes with the server */
+
+#define AT_RECURSIVE		0x8000	/* Apply to the entire subtree */
+
 /* Values for the WHENCE argument to lseek.  */
 #define SEEK_SET	0	/* Seek from beginning of file.  */
 #define SEEK_CUR	1	/* Seek from current position.  */
@@ -125,6 +152,10 @@ static int open(uintptr_t pathname, int flags, mode_t mode) {
 	return (int) bpf_task_sys_open_3((uint64_t) pathname, flags, mode);
 }
 
+static int openat(int dfd, void *pathname, int flags, mode_t mode) {
+	return (int) bpf_task_sys_openat_4(dfd, (uint64_t) pathname, flags, mode);
+}
+
 static ssize_t read(int fd, void *buf, size_t count) {
 	return (ssize_t) bpf_task_sys_read_3(fd, (uint64_t) buf, count);
 }
-- 
GitLab