-
Notifications
You must be signed in to change notification settings - Fork 1
feat(ebpf): use bpf_loop for local d_path implementation #175
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,74 @@ | ||
| #pragma once | ||
|
|
||
| // clang-format off | ||
| #include "maps.h" | ||
| #include "vmlinux.h" | ||
|
|
||
| #include "maps.h" | ||
|
|
||
| #include <bpf/bpf_helpers.h> | ||
| #include <bpf/bpf_core_read.h> | ||
| // clang-format on | ||
|
|
||
| struct d_path_ctx { | ||
| struct helper_t* helper; | ||
| struct path root; | ||
| struct mount* mnt; | ||
| struct dentry* dentry; | ||
| int offset; | ||
| int buflen; | ||
| bool success; | ||
| }; | ||
|
|
||
| static long __d_path_inner(uint32_t index, void* _ctx) { | ||
| struct d_path_ctx* ctx = (struct d_path_ctx*)_ctx; | ||
| struct dentry* dentry = ctx->dentry; | ||
| struct dentry* parent = BPF_CORE_READ(dentry, d_parent); | ||
| struct mount* mnt = ctx->mnt; | ||
| struct dentry* mnt_root = BPF_CORE_READ(mnt, mnt.mnt_root); | ||
|
|
||
| if (dentry == mnt_root) { | ||
| struct mount* m = BPF_CORE_READ(mnt, mnt_parent); | ||
| if (m != mnt) { | ||
| ctx->dentry = BPF_CORE_READ(mnt, mnt_mountpoint); | ||
| ctx->mnt = m; | ||
| return 0; | ||
| } | ||
| ctx->success = true; | ||
| return 1; | ||
| } | ||
|
|
||
| if (dentry == parent) { | ||
| return 1; | ||
| } | ||
|
|
||
| struct qstr d_name; | ||
| BPF_CORE_READ_INTO(&d_name, dentry, d_name); | ||
| int len = d_name.len & (PATH_MAX - 1); | ||
| if (len <= 0 || len >= ctx->buflen) { | ||
| return 1; | ||
| } | ||
|
|
||
| int offset = ctx->offset - len; | ||
| if (offset <= 0) { | ||
| return 1; | ||
| } | ||
| offset &= PATH_MAX - 1; | ||
|
|
||
| if (bpf_probe_read_kernel(&ctx->helper->buf[offset], len, d_name.name) != 0) { | ||
| return 1; | ||
| } | ||
|
|
||
| offset--; | ||
| if (offset <= 0) { | ||
| return 1; | ||
| } | ||
| ctx->helper->buf[offset] = '/'; | ||
|
|
||
| ctx->offset = offset; | ||
| ctx->dentry = parent; | ||
| return 0; | ||
| } | ||
|
|
||
| /** | ||
| * Reimplementation of the kernel d_path function. | ||
| * | ||
|
|
@@ -19,66 +80,31 @@ __always_inline static long __d_path(const struct path* path, char* buf, int buf | |
| return -1; | ||
| } | ||
|
|
||
| struct helper_t* helper = get_helper(); | ||
| if (helper == NULL) { | ||
| int offset = (buflen - 1) & (PATH_MAX - 1); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I overlooked this originally, but I think I need some explanation about this part. I assume the intention is to get the offset that fits into Then
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So this is mostly to keep the verifier happy. The max length of a path in Linux is defined to be 4096 bytes, which when substracting 1 from it ends up with a binary number that is all 1s as you pointed out, so doing this bitwise and is just a convenient way to have the verifier not complain about The entire logic behind this implementation relies on I should probably do something about better documenting these invariants, at the moment they are just sitting there. |
||
| struct d_path_ctx ctx = { | ||
| .buflen = buflen, | ||
| .helper = get_helper(), | ||
| .offset = offset, | ||
| }; | ||
|
|
||
| if (ctx.helper == NULL) { | ||
| return -1; | ||
| } | ||
|
|
||
| struct task_struct* task = (struct task_struct*)bpf_get_current_task(); | ||
| int offset = (buflen - 1) & (PATH_MAX - 1); | ||
| helper->buf[offset] = '\0'; // Ensure null termination | ||
|
|
||
| struct path root; | ||
| BPF_CORE_READ_INTO(&root, task, fs, root); | ||
| struct mount* mnt = container_of(path->mnt, struct mount, mnt); | ||
| struct dentry* dentry; | ||
| BPF_CORE_READ_INTO(&dentry, path, dentry); | ||
|
|
||
| for (int i = 0; i < 16 && (dentry != root.dentry || &mnt->mnt != root.mnt); i++) { | ||
| struct dentry* parent = BPF_CORE_READ(dentry, d_parent); | ||
| struct dentry* mnt_root = BPF_CORE_READ(mnt, mnt.mnt_root); | ||
|
|
||
| if (dentry == mnt_root) { | ||
| struct mount* m = BPF_CORE_READ(mnt, mnt_parent); | ||
| if (m != mnt) { | ||
| dentry = BPF_CORE_READ(mnt, mnt_mountpoint); | ||
| mnt = m; | ||
| continue; | ||
| } | ||
| break; | ||
| } | ||
|
|
||
| if (dentry == parent) { | ||
| return -1; | ||
| } | ||
| ctx.helper->buf[offset] = '\0'; // Ensure null termination | ||
|
|
||
| struct qstr d_name; | ||
| BPF_CORE_READ_INTO(&d_name, dentry, d_name); | ||
| int len = d_name.len; | ||
| if (len <= 0 || len >= buflen) { | ||
| return -1; | ||
| } | ||
| BPF_CORE_READ_INTO(&ctx.root, task, fs, root); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How much if
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can try it out but I can already think of a couple reason why this might not work:
|
||
| ctx.mnt = container_of(path->mnt, struct mount, mnt); | ||
| BPF_CORE_READ_INTO(&ctx.dentry, path, dentry); | ||
|
|
||
| offset -= len; | ||
| if (offset <= 0) { | ||
| return -1; | ||
| } | ||
|
|
||
| if (bpf_probe_read_kernel(&helper->buf[offset], len, d_name.name) != 0) { | ||
| return -1; | ||
| } | ||
|
|
||
| offset--; | ||
| if (offset <= 0) { | ||
| return -1; | ||
| } | ||
| helper->buf[offset] = '/'; | ||
|
|
||
| dentry = parent; | ||
| long res = bpf_loop(PATH_MAX, __d_path_inner, &ctx, 0); | ||
| if (res <= 0 || !ctx.success) { | ||
| return -1; | ||
| } | ||
|
|
||
| bpf_probe_read_str(buf, buflen, &helper->buf[offset]); | ||
| return buflen - offset; | ||
| bpf_probe_read_str(buf, buflen, &ctx.helper->buf[ctx.offset & (PATH_MAX - 1)]); | ||
| return buflen - ctx.offset; | ||
| } | ||
|
|
||
| __always_inline static long d_path(struct path* path, char* buf, int buflen, bool use_bpf_helper) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do I understand correctly, that this case is not considered to be a "success"? If so, why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The successful case for
d_pathis to reach the mount root of the process that is currently running, this condition happens when something goes wrong traversing the mounts and we end up in a position where there are not more parents in the directory cache (usually reaching the root of the device).I can add some clarifying comments based on the kernel implementation of
d_path