Repository navigation
Chore/retire headless proc regex in favor of opens-gadget fix -- remove band-aid from last week #889
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
Merged
matthyx
merged 4 commits into
kubescape:main
from
k8sstormcenter:chore/retire-headless-proc-regex
Aug 14, 2026
Merged
Chore/retire headless proc regex in favor of opens-gadget fix -- remove band-aid from last week #889
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
df1d604
gadget(trace_open): resolve relative opens against dirfd/cwd
ConstanzeTU d2d93bb
utils: drop headless /proc re-rooting from NormalizePath
ConstanzeTU 3e8e793
addressing the review
entlein 4ea674e
addressing Matthias suggestions, now rerunning the 3 side compare
entlein File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| IMAGE?=ghcr.io/inspektor-gadget/gadget/trace_open | ||
| TAG?=v0.48.1 | ||
| BUILDER_IMAGE?=ghcr.io/inspektor-gadget/gadget-builder@sha256:e0e88543a99bbc4b1263b7f9d07a08fb8f1107426525ef8b777bdd8df15d1ee8 | ||
|
|
||
| build: | ||
| sudo TMPDIR=$$TMPDIR ig image build -t $(IMAGE):$(TAG) . --update-metadata --builder-image $(BUILDER_IMAGE) | ||
|
|
||
| run: | ||
| sudo ig run $(IMAGE):$(TAG) --verify-image=false -o jsonpretty | ||
|
|
||
| build-and-run: build run |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,260 @@ | ||
| // Based on | ||
| // https://github.com/aquasecurity/tracee/blob/bd80c1d9e69e275f06810f2a0f99414aced14fa8/pkg/ebpf/c/common/filesystem.h | ||
|
|
||
| #ifndef __COMMON_FILESYSTEM_H__ | ||
| #define __COMMON_FILESYSTEM_H__ | ||
|
|
||
| // clang-format off | ||
| #define MAX_PERCPU_BUFSIZE (1 << 15) // set by the kernel as an upper bound | ||
| #define GADGET_PATH_MAX 512 // smaller than PATH_MAX to reduce memory consumption | ||
| #define PATH_MAX 4096 | ||
| #define MAX_STR_FILTER_SIZE 16 // bounded to size of the compared values (comm) | ||
| #define MAX_BIN_PATH_SIZE 256 // max binary path size | ||
| #define FILE_MAGIC_HDR_SIZE 32 // magic_write: bytes to save from a file's header | ||
| #define FILE_MAGIC_MASK 31 // magic_write: mask used for verifier boundaries | ||
| #define NET_SEQ_OPS_SIZE 4 // print_net_seq_ops: struct size - TODO: replace with uprobe argument | ||
| #define NET_SEQ_OPS_TYPES 6 // print_net_seq_ops: argument size - TODO: replace with uprobe argument | ||
| #define MAX_KSYM_NAME_SIZE 64 | ||
| #define UPROBE_MAGIC_NUMBER 20220829 | ||
| #define ARGS_BUF_SIZE 32000 | ||
| #define SEND_META_SIZE 24 | ||
| #define MAX_MEM_DUMP_SIZE 127 | ||
|
|
||
| #define MAX_PATH_COMPONENTS 80 | ||
|
|
||
| // memory related | ||
| enum buf_idx_e | ||
| { | ||
| STRING_BUF_IDX, | ||
| //FILE_BUF_IDX, | ||
| MAX_BUFFERS | ||
| }; | ||
|
|
||
| typedef struct simple_buf { | ||
| u8 buf[MAX_PERCPU_BUFSIZE]; | ||
| } buf_t; | ||
|
|
||
| #define BPF_MAP(_name, _type, _key_type, _value_type, _max_entries) \ | ||
| struct { \ | ||
| __uint(type, _type); \ | ||
| __uint(max_entries, _max_entries); \ | ||
| __type(key, _key_type); \ | ||
| __type(value, _value_type); \ | ||
| } _name SEC(".maps"); | ||
|
|
||
|
|
||
| #define BPF_PERCPU_ARRAY(_name, _value_type, _max_entries) \ | ||
| BPF_MAP(_name, BPF_MAP_TYPE_PERCPU_ARRAY, u32, _value_type, _max_entries) | ||
|
|
||
| BPF_PERCPU_ARRAY(bufs, buf_t, MAX_BUFFERS); // percpu global buffer variables | ||
|
|
||
| // undef as we don't want to use this in our gadgets. yet? | ||
| #undef BPF_MAP | ||
| #undef BPF_PERCPU_ARRAY | ||
|
|
||
| static __always_inline buf_t *get_buf(int idx) | ||
| { | ||
| return bpf_map_lookup_elem(&bufs, &idx); | ||
| } | ||
|
|
||
| static __always_inline struct dentry *get_mnt_root_ptr_from_vfsmnt(struct vfsmount *vfsmnt) | ||
| { | ||
| return BPF_CORE_READ(vfsmnt, mnt_root); | ||
| } | ||
|
|
||
| static __always_inline struct dentry *get_d_parent_ptr_from_dentry(struct dentry *dentry) | ||
| { | ||
| return BPF_CORE_READ(dentry, d_parent); | ||
| } | ||
|
|
||
| static inline struct mount *real_mount(struct vfsmount *mnt) | ||
| { | ||
| return container_of(mnt, struct mount, mnt); | ||
| } | ||
|
|
||
| static __always_inline struct qstr get_d_name_from_dentry(struct dentry *dentry) | ||
| { | ||
| return BPF_CORE_READ(dentry, d_name); | ||
| } | ||
|
|
||
| static __always_inline void *get_path_str(struct path *path) | ||
| { | ||
| struct path f_path; | ||
| bpf_probe_read(&f_path, sizeof(struct path), path); | ||
| char slash = '/'; | ||
| int zero = 0; | ||
| struct dentry *dentry = f_path.dentry; | ||
| struct vfsmount *vfsmnt = f_path.mnt; | ||
| struct mount *mnt_parent_p; | ||
|
|
||
| struct mount *mnt_p = real_mount(vfsmnt); | ||
| bpf_probe_read(&mnt_parent_p, sizeof(struct mount *), &mnt_p->mnt_parent); | ||
|
|
||
| u32 buf_off = (MAX_PERCPU_BUFSIZE >> 1); | ||
| struct dentry *mnt_root; | ||
| struct dentry *d_parent; | ||
| struct qstr d_name; | ||
| unsigned int len; | ||
| unsigned int off; | ||
| int sz; | ||
|
|
||
| // Get per-cpu string buffer | ||
| buf_t *string_p = get_buf(STRING_BUF_IDX); | ||
| if (string_p == NULL) | ||
| return NULL; | ||
|
|
||
| #pragma unroll | ||
| for (int i = 0; i < MAX_PATH_COMPONENTS; i++) { | ||
| mnt_root = get_mnt_root_ptr_from_vfsmnt(vfsmnt); | ||
| d_parent = get_d_parent_ptr_from_dentry(dentry); | ||
| if (dentry == mnt_root || dentry == d_parent) { | ||
| if (dentry != mnt_root) { | ||
| // We reached root, but not mount root - escaped? | ||
| break; | ||
| } | ||
| if (mnt_p != mnt_parent_p) { | ||
| // We reached root, but not global root - continue with mount point path | ||
| bpf_probe_read(&dentry, sizeof(struct dentry *), &mnt_p->mnt_mountpoint); | ||
| bpf_probe_read(&mnt_p, sizeof(struct mount *), &mnt_p->mnt_parent); | ||
| bpf_probe_read(&mnt_parent_p, sizeof(struct mount *), &mnt_p->mnt_parent); | ||
| vfsmnt = &mnt_p->mnt; | ||
| continue; | ||
| } | ||
| // Global root - path fully parsed | ||
| break; | ||
| } | ||
| // Add this dentry name to path | ||
| d_name = get_d_name_from_dentry(dentry); | ||
| len = (d_name.len + 1) & (PATH_MAX - 1); | ||
| off = buf_off - len; | ||
|
|
||
| // Is string buffer big enough for dentry name? | ||
| sz = 0; | ||
| if (off <= buf_off) { // verify no wrap occurred | ||
| len = len & ((MAX_PERCPU_BUFSIZE >> 1) - 1); | ||
| sz = bpf_probe_read_str( | ||
| &(string_p->buf[off & ((MAX_PERCPU_BUFSIZE >> 1) - 1)]), len, (void *) d_name.name); | ||
| } else | ||
| break; | ||
| if (sz > 1) { | ||
| buf_off -= 1; // remove null byte termination with slash sign | ||
| bpf_probe_read(&(string_p->buf[buf_off & (MAX_PERCPU_BUFSIZE - 1)]), 1, &slash); | ||
| buf_off -= sz - 1; | ||
| } else { | ||
| // If sz is 0 or 1 we have an error (path can't be null nor an empty string) | ||
| break; | ||
| } | ||
| dentry = d_parent; | ||
| } | ||
|
|
||
| if (buf_off == (MAX_PERCPU_BUFSIZE >> 1)) { | ||
| buf_off = 0; | ||
| d_name = get_d_name_from_dentry(dentry); | ||
| sz = bpf_probe_read_str(&(string_p->buf[0]), PATH_MAX, (void *) d_name.name); | ||
| if (sz <= 1) | ||
| return NULL; | ||
| } else { | ||
| // Add leading slash | ||
| buf_off -= 1; | ||
| bpf_probe_read(&(string_p->buf[buf_off & (MAX_PERCPU_BUFSIZE - 1)]), 1, &slash); | ||
| // Null terminate the path string | ||
| bpf_probe_read(&(string_p->buf[(MAX_PERCPU_BUFSIZE >> 1) - 1]), 1, &zero); | ||
| } | ||
|
|
||
| return &string_p->buf[buf_off]; | ||
| } | ||
|
|
||
| // Function to extract file structure from a user space file descriptor | ||
| static __always_inline struct file * get_struct_file_for_fd(int fd_num) | ||
| { | ||
| if (fd_num < 0) { | ||
| return NULL; | ||
| } | ||
|
|
||
| struct task_struct *task = (struct task_struct *) bpf_get_current_task(); | ||
| if (task == NULL) { | ||
| return NULL; | ||
| } | ||
|
|
||
| // extract the file vector from the task_struct | ||
| struct file **fd = BPF_CORE_READ(task, files, fdt, fd); | ||
|
|
||
| // extract the file pointer from the file vector | ||
| struct file *f = NULL; | ||
| uint max_fds = BPF_CORE_READ(task, files, fdt, max_fds); | ||
| if (fd_num < max_fds) { | ||
| bpf_core_read((void *) &f, sizeof(f), &fd[fd_num]); | ||
| } | ||
|
|
||
| return f; | ||
| } | ||
|
|
||
| static __always_inline long read_full_path_of_open_file_fd(int fd_num, char *buf, u64 buf_len) | ||
| { | ||
| struct file *file = get_struct_file_for_fd(fd_num); | ||
| if (file == NULL) { | ||
| return -1; | ||
| } | ||
|
|
||
| struct path f_path = BPF_CORE_READ(file, f_path); | ||
|
|
||
| // Extract the full path string | ||
| char* c_path = get_path_str(&f_path); | ||
| if (!c_path) { | ||
| return -1; | ||
| } | ||
| return bpf_probe_read_kernel_str(buf, buf_len, c_path); | ||
| } | ||
|
|
||
|
|
||
| #ifndef AT_FDCWD | ||
| #define AT_FDCWD -100 | ||
| #endif | ||
|
|
||
| // buf is always GADGET_PATH_MAX bytes: the verifier needs a compile-time bound | ||
| // for the masked writes below, so the size is not a runtime parameter. | ||
| static __always_inline long read_full_path_of_dfd_rel(int dfd, const char *user_fname, | ||
| char *buf) | ||
| { | ||
| struct path base; | ||
|
|
||
| if (dfd == AT_FDCWD) { | ||
| struct task_struct *task = (struct task_struct *) bpf_get_current_task(); | ||
| if (!task) | ||
| return -1; | ||
| base = BPF_CORE_READ(task, fs, pwd); | ||
| } else { | ||
| struct file *f = get_struct_file_for_fd(dfd); | ||
| if (!f) | ||
| return -1; | ||
| base = BPF_CORE_READ(f, f_path); | ||
| } | ||
|
|
||
| char *bstr = get_path_str(&base); | ||
| if (!bstr) | ||
| return -1; | ||
|
|
||
| long n = bpf_probe_read_kernel_str(buf, GADGET_PATH_MAX, bstr); | ||
| if (n <= 1) | ||
| return -1; | ||
|
|
||
| u32 off = (u32) (n - 1); | ||
| #define REL_NAME_MAX 256 | ||
| // Base too long to append the name within GADGET_PATH_MAX: fail closed | ||
| // rather than truncate the base into a plausible but wrong absolute path. | ||
| if (off >= GADGET_PATH_MAX - REL_NAME_MAX) | ||
| return -1; | ||
| // Redundant given the check above, but the verifier needs the constant | ||
| // mask to prove the REL_NAME_MAX write below stays in bounds. | ||
| off &= (GADGET_PATH_MAX - REL_NAME_MAX - 1); | ||
| buf[off & (GADGET_PATH_MAX - 1)] = '/'; | ||
| long m = bpf_probe_read_user_str(&buf[(off + 1) & (GADGET_PATH_MAX - 1)], | ||
| REL_NAME_MAX - 1, user_fname); | ||
| if (m <= 1) { | ||
| buf[off & (GADGET_PATH_MAX - 1)] = '\0'; | ||
| return -1; | ||
| } | ||
| return off + m; | ||
|
coderabbitai[bot] marked this conversation as resolved.
Resolved
|
||
| } | ||
|
|
||
| #endif | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| name: trace open | ||
| description: trace open files | ||
| homepageURL: https://inspektor-gadget.io/ | ||
| documentationURL: https://www.inspektor-gadget.io/docs/latest/gadgets/trace_open | ||
| sourceURL: https://github.com/inspektor-gadget/inspektor-gadget/tree/main/gadgets/trace_open | ||
| datasources: | ||
| open: | ||
| fields: | ||
| error_raw: | ||
| annotations: | ||
| columns.hidden: "true" | ||
| fd: | ||
| annotations: | ||
| columns.alignment: right | ||
| columns.maxwidth: "3" | ||
| columns.minwidth: "2" | ||
| description: File descriptor. 0 in case of error | ||
| flags: | ||
| annotations: | ||
| columns.hidden: "true" | ||
| columns.width: "10" | ||
| flags_raw: | ||
| annotations: | ||
| columns.hidden: "true" | ||
| fname: | ||
| annotations: | ||
| columns.minwidth: "24" | ||
| columns.width: "32" | ||
| description: File name as given in the open syscall | ||
| fpath: | ||
| annotations: | ||
| columns.minwidth: "24" | ||
| columns.width: "32" | ||
| description: Full file path after symlink resolution (require --paths flag) | ||
| mode: | ||
| annotations: | ||
| description: File access mode | ||
| mode_raw: | ||
| annotations: | ||
| columns.hidden: "true" | ||
| proc: | ||
| annotations: | ||
| description: 'TODO: Fill field description' | ||
| timestamp_raw: | ||
| annotations: | ||
| description: 'TODO: Fill field description' | ||
| ustack: | ||
| annotations: | ||
| description: 'TODO: Fill field description' | ||
| params: | ||
| ebpf: | ||
| collect_build_id: | ||
| key: collect_build_id | ||
| defaultValue: "" | ||
| description: 'TODO: Fill parameter description' | ||
| collect_ustack: | ||
| key: collect_ustack | ||
| defaultValue: "" | ||
| description: 'TODO: Fill parameter description' | ||
| ig_build_id_max_entries: | ||
| key: ig_build_id_max_entries | ||
| defaultValue: "" | ||
| description: 'TODO: Fill parameter description' | ||
| paths: | ||
| key: paths | ||
| defaultValue: "false" | ||
| description: Show file path after symlink resolution | ||
| targ_comm: | ||
| key: targ_comm | ||
| defaultValue: "" | ||
| description: 'TODO: Fill parameter description' | ||
| targ_failed: | ||
| key: failed | ||
| defaultValue: "false" | ||
| description: Show only failed events | ||
| targ_gid: | ||
| key: targ_gid | ||
| defaultValue: "" | ||
| description: 'TODO: Fill parameter description' | ||
| targ_pid: | ||
| key: targ_pid | ||
| defaultValue: "" | ||
| description: 'TODO: Fill parameter description' | ||
| targ_tid: | ||
| key: targ_tid | ||
| defaultValue: "" | ||
| description: 'TODO: Fill parameter description' | ||
| targ_uid: | ||
| key: targ_uid | ||
| defaultValue: "" | ||
| description: 'TODO: Fill parameter description' |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Nit:
read_full_path_of_dfd_reltakes abuf_lenparameter but ignores it, hardcodingGADGET_PATH_MAXhere and at lines 244-245 instead. Not a live bug — the only call site (program.bpf.c) always passessizeof(event->fpath), which is exactlyGADGET_PATH_MAX— but it's a landmine if this helper ever gets reused with a differently-sized buffer, since the masking arithmetic (& (GADGET_PATH_MAX - 1)) would silently wrap into the caller's buffer using the wrong size instead of bounding against the realbuf_len. Worth usingbuf_len(and rejecting if it doesn't fit) to make the signature honest.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.
Resolved by dropping the parameter:
bufis always theGADGET_PATH_MAX-sizedfpath, and wiring a runtime length into the masked append is rejected by the verifier (invalid access to memory, mem_size=912 off=911 size=255) — the masks need a compile-time-constant bound. Using the true buffer size (GADGET_PATH_MAX) throughout keeps the signature honest without the dead parameter.