-
Notifications
You must be signed in to change notification settings - Fork 4
ROX-33217: Instrument inode tracking on directory being created path mkdir #465
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
6e63d30
0682fb2
7aecaa4
e01ccb1
9a7a03e
ab17d26
3aa4998
5288866
99ee8cb
be580e1
2974373
b0ef4fe
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 |
|---|---|---|
|
|
@@ -19,6 +19,12 @@ char _license[] SEC("license") = "Dual MIT/GPL"; | |
| #define FMODE_PWRITE ((fmode_t)(1 << 4)) | ||
| #define FMODE_CREATED ((fmode_t)(1 << 20)) | ||
|
|
||
| // File type constants from linux/stat.h | ||
| // https://github.com/torvalds/linux/blob/5619b098e2fbf3a23bf13d91897056a1fe238c6d/include/uapi/linux/stat.h | ||
| #define S_IFMT 00170000 | ||
| #define S_IFDIR 0040000 | ||
| #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) | ||
|
|
||
| SEC("lsm/file_open") | ||
| int BPF_PROG(trace_file_open, struct file* file) { | ||
| struct metrics_t* m = get_metrics(); | ||
|
|
@@ -228,3 +234,99 @@ int BPF_PROG(trace_path_rename, struct path* old_dir, | |
| m->path_rename.error++; | ||
| return 0; | ||
| } | ||
|
|
||
| SEC("lsm/path_mkdir") | ||
| int BPF_PROG(trace_path_mkdir, struct path* dir, struct dentry* dentry, umode_t mode) { | ||
| struct metrics_t* m = get_metrics(); | ||
| if (m == NULL) { | ||
| return 0; | ||
| } | ||
|
|
||
| m->path_mkdir.total++; | ||
|
|
||
| struct bound_path_t* path = path_read_append_d_entry(dir, dentry); | ||
| if (path == NULL) { | ||
| bpf_printk("Failed to read path"); | ||
| m->path_mkdir.error++; | ||
| return 0; | ||
| } | ||
|
|
||
| struct inode* parent_inode_ptr = BPF_CORE_READ(dir, dentry, d_inode); | ||
| inode_key_t parent_inode = inode_to_key(parent_inode_ptr); | ||
|
|
||
| if (should_track_mkdir(parent_inode, path) != PARENT_MONITORED) { | ||
|
Contributor
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 originally had However, |
||
| m->path_mkdir.ignored++; | ||
| return 0; | ||
| } | ||
|
|
||
| // Stash mkdir context for security_d_instantiate | ||
| __u64 pid_tgid = bpf_get_current_pid_tgid(); | ||
| struct mkdir_context_t* mkdir_ctx = get_mkdir_context(); | ||
| if (mkdir_ctx == NULL) { | ||
| bpf_printk("Failed to get mkdir context buffer"); | ||
| m->path_mkdir.error++; | ||
| return 0; | ||
| } | ||
|
|
||
| long path_copy_len = bpf_probe_read_str(mkdir_ctx->path, PATH_MAX, path->path); | ||
| if (path_copy_len < 0) { | ||
| bpf_printk("Failed to copy path string"); | ||
| m->path_mkdir.error++; | ||
| return 0; | ||
| } | ||
| mkdir_ctx->parent_inode = parent_inode; | ||
|
|
||
| if (bpf_map_update_elem(&mkdir_context, &pid_tgid, mkdir_ctx, BPF_ANY) != 0) { | ||
| bpf_printk("Failed to stash mkdir context"); | ||
| m->path_mkdir.error++; | ||
| return 0; | ||
| } | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| SEC("lsm/d_instantiate") | ||
| int BPF_PROG(trace_d_instantiate, struct dentry* dentry, struct inode* inode) { | ||
| struct metrics_t* m = get_metrics(); | ||
| if (m == NULL) { | ||
| return 0; | ||
| } | ||
|
|
||
| m->d_instantiate.total++; | ||
|
|
||
| __u64 pid_tgid = bpf_get_current_pid_tgid(); | ||
| struct mkdir_context_t* mkdir_ctx = bpf_map_lookup_elem(&mkdir_context, &pid_tgid); | ||
|
|
||
| if (mkdir_ctx == NULL) { | ||
| m->d_instantiate.ignored++; | ||
| return 0; | ||
| } | ||
|
|
||
| if (inode == NULL) { | ||
| m->d_instantiate.ignored++; | ||
| goto cleanup; | ||
| } | ||
|
|
||
| umode_t mode = BPF_CORE_READ(inode, i_mode); | ||
| if (!S_ISDIR(mode)) { | ||
| m->d_instantiate.ignored++; | ||
| goto cleanup; | ||
| } | ||
|
Comment on lines
+310
to
+314
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. We should check if |
||
|
|
||
| inode_key_t inode_key = inode_to_key(inode); | ||
|
|
||
| if (inode_add(&inode_key) == 0) { | ||
| m->d_instantiate.added++; | ||
| } else { | ||
| m->d_instantiate.error++; | ||
| } | ||
|
|
||
| submit_mkdir_event(&m->d_instantiate, | ||
| mkdir_ctx->path, | ||
| &inode_key, | ||
| &mkdir_ctx->parent_inode); | ||
|
|
||
| cleanup: | ||
| bpf_map_delete_elem(&mkdir_context, &pid_tgid); | ||
| return 0; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -83,6 +83,26 @@ struct { | |
| __uint(map_flags, BPF_F_NO_PREALLOC); | ||
| } inode_map SEC(".maps"); | ||
|
|
||
| struct { | ||
| __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); | ||
| __type(key, __u32); | ||
| __type(value, struct mkdir_context_t); | ||
| __uint(max_entries, 1); | ||
| } mkdir_context_heap SEC(".maps"); | ||
|
|
||
| __always_inline static struct mkdir_context_t* get_mkdir_context() { | ||
| unsigned int zero = 0; | ||
| return bpf_map_lookup_elem(&mkdir_context_heap, &zero); | ||
| } | ||
|
Comment on lines
+86
to
+96
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. This seems to be unused.
Contributor
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. This is used. |
||
|
|
||
| struct { | ||
| __uint(type, BPF_MAP_TYPE_HASH); | ||
| __type(key, __u64); | ||
| __type(value, struct mkdir_context_t); | ||
| __uint(max_entries, 16384); | ||
| __uint(map_flags, BPF_F_NO_PREALLOC); | ||
| } mkdir_context SEC(".maps"); | ||
|
|
||
| struct { | ||
| __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); | ||
| __type(key, __u32); | ||
|
|
||
|
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 think you also need to add your metrics here: Lines 123 to 127 in eb0b3a9
I will look into improving how we handle these, it is getting very repetitive and having it scattered among multiple files is very error prone. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| import os | ||
|
|
||
| import pytest | ||
|
|
||
| from event import Event, EventType, Process | ||
|
|
||
|
|
||
| def test_mkdir_nested(monitored_dir, server): | ||
|
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. Maybe we can parametrize the final directory with UTF-8 characters? |
||
| """ | ||
| Tests that creating nested directories tracks all inodes correctly. | ||
|
|
||
| Args: | ||
| monitored_dir: Temporary directory path for creating the test directory. | ||
| server: The server instance to communicate with. | ||
| """ | ||
| process = Process.from_proc() | ||
|
|
||
| # Create nested directories | ||
| level1 = os.path.join(monitored_dir, 'level1') | ||
| level2 = os.path.join(level1, 'level2') | ||
| level3 = os.path.join(level2, 'level3') | ||
|
|
||
| os.makedirs(level3, exist_ok=True) | ||
|
|
||
| # Create a file in the deepest directory | ||
| test_file = os.path.join(level3, 'deep_file.txt') | ||
| with open(test_file, 'w') as f: | ||
| f.write('nested content') | ||
|
|
||
| events = [ | ||
| Event(process=process, event_type=EventType.CREATION, | ||
| file=level1, host_path=level1), | ||
| Event(process=process, event_type=EventType.CREATION, | ||
| file=level2, host_path=level2), | ||
| Event(process=process, event_type=EventType.CREATION, | ||
| file=level3, host_path=level3), | ||
| Event(process=process, event_type=EventType.CREATION, | ||
| file=test_file, host_path=test_file), | ||
| ] | ||
|
|
||
| server.wait_events(events) | ||
|
|
||
|
|
||
| def test_mkdir_ignored(monitored_dir, ignored_dir, server): | ||
| """ | ||
| Tests that directories created outside monitored paths are ignored. | ||
|
|
||
| Args: | ||
| monitored_dir: Temporary directory path that is monitored. | ||
| ignored_dir: Temporary directory path that is not monitored. | ||
| server: The server instance to communicate with. | ||
| """ | ||
| process = Process.from_proc() | ||
|
|
||
| # Create directory in ignored path - should not be tracked | ||
| ignored_subdir = os.path.join(ignored_dir, 'ignored_subdir') | ||
| os.mkdir(ignored_subdir) | ||
|
|
||
| # Create directory in monitored path - should be tracked | ||
| monitored_subdir = os.path.join(monitored_dir, 'monitored_subdir') | ||
| os.mkdir(monitored_subdir) | ||
|
|
||
| # Only the monitored directory should generate an event | ||
| e = Event(process=process, event_type=EventType.CREATION, | ||
| file=monitored_subdir, host_path=monitored_subdir) | ||
|
|
||
| server.wait_events([e]) | ||
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.
Could we add a permalink to the definition?
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.
Done