-
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?
Conversation
82a7bc8 to
c2f31a8
Compare
Part of ROX-32059. The local implementation of d_path now uses bpf_loop, which enables it to iterate further than the previously implemented loop that did at most 16 levels of path.
c2f31a8 to
3edf813
Compare
ovalenti
left a comment
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.
I read it carefully, and it made sense !
| if (len <= 0 || len >= buflen) { | ||
| return -1; | ||
| } | ||
| BPF_CORE_READ_INTO(&ctx.root, task, fs, root); |
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.
How much if BPF_CORE_READ/BPF_CORE_READ_INFO is actually needed in this patch? I've tried to use bpf_get_current_task_btf, other structs seems to be already BTF enhanced -- and at least on Fedora simple access without the macro works fine.
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.
I can try it out but I can already think of a couple reason why this might not work:
- A lot of the logic in this
d_pathimplementation relies on thecontainer_ofcast tostruct mount, I'm fairly certain that call loses all BTF information, but I need to test it. - Going through
bpf_loopmeans we use an intermediate struct that needs casting, fairly certain that also messes with the BTF information of types.
| return 1; | ||
| } | ||
|
|
||
| if (dentry == parent) { |
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_path is 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
|
|
||
| struct helper_t* helper = get_helper(); | ||
| if (helper == NULL) { | ||
| int offset = (buflen - 1) & (PATH_MAX - 1); |
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.
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 PATH_MAX, right? If yes, it seems to work only because PATH_MAX value is 2^n, so that PATH_MAX - 1 will be all ones. But I don't get it how it supposed to work, if buflen is larger than PATH_MAX -- e.g. if I call d_path as:
d_path(&task->mm->exe_file->f_path, p->lineage[i].exe_path, 4097, false);
Then (buflen - 1) & (PATH_MAX - 1) is 1, and the filepath is not copied:
{"timestamp":1766410173613705048,"hostname":"xxxx","process":{"comm":"fish","args":["-fish"],"exe_path":"","container_id":null,"uid":4206644,"username":"","gid":4206644,"login_uid":4206644,"pid":70608,"in_root_mount_ns":true,"lineage":[{"uid":4206644,"exe_path":""},{"uid":0,"exe_path":""}]},"file":{"Open":{"filename":"","host_file":"/file/path/test","inode":{"inode":55138507,"dev":40}}}}
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.
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 offset being unbound.
The entire logic behind this implementation relies on PATH_MAX being 4K and the buffer length provided never being greater than PATH_MAX, these are invariants and if broken, then behavior will be undefined.
I should probably do something about better documenting these invariants, at the moment they are just sitting there.
Description
Part of ROX-32059.
The local implementation of d_path now uses bpf_loop, which enables it to iterate further than the previously implemented loop that did at most 16 levels of path.
#156 needs this change because the path_chmod hook is throwing a verifier error with too many instructions.
Checklist
Automated testing
If any of these don't apply, please comment below.
Testing Performed