-
Notifications
You must be signed in to change notification settings - Fork 4
ROX-33198: Instrument inode tracking on file open lsm hook #391
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 |
|---|---|---|
|
|
@@ -126,7 +126,7 @@ impl HostScanner { | |
| for entry in glob::glob(glob_str)? { | ||
| match entry { | ||
| Ok(path) => { | ||
| if path.is_file() { | ||
| if path.is_file() || path.is_dir() { | ||
| self.metrics.scan_inc(ScanLabels::FileScanned); | ||
| self.update_entry(path.as_path()).with_context(|| { | ||
| format!("Failed to update entry for {}", path.display()) | ||
|
|
@@ -178,6 +178,25 @@ impl HostScanner { | |
| self.inode_map.borrow().get(inode?).cloned() | ||
| } | ||
|
|
||
| /// Handle file creation events by adding new inodes to the map. | ||
| fn handle_creation_event(&self, event: &Event) -> anyhow::Result<()> { | ||
| if self.get_host_path(Some(event.get_inode())).is_some() { | ||
| return Ok(()); | ||
| } | ||
|
|
||
| let host_path = host_info::prepend_host_mount(event.get_filename()); | ||
|
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. The initial plan was to look up the parent, get its file path, and then add the file name. However, it seems possible to get the entire path from the event. |
||
|
|
||
| if host_path.exists() { | ||
| self.update_entry(&host_path) | ||
| .with_context(|| format!("Failed to add creation event entry for {}", host_path.display()))?; | ||
| } else { | ||
| debug!("Creation event for non-existent file: {}", host_path.display()); | ||
| self.metrics.scan_inc(ScanLabels::FileRemoved); | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| /// Periodically notify the host scanner main task that a scan needs | ||
| /// to happen. | ||
| /// | ||
|
|
@@ -219,6 +238,13 @@ impl HostScanner { | |
| }; | ||
| self.metrics.events.added(); | ||
|
|
||
| // Handle file creation events by adding new inodes to the map | ||
| if event.is_creation() { | ||
| if let Err(e) = self.handle_creation_event(&event) { | ||
| warn!("Failed to handle creation event: {e}"); | ||
| } | ||
| } | ||
|
|
||
| if let Some(host_path) = self.get_host_path(Some(event.get_inode())) { | ||
| self.metrics.scan_inc(ScanLabels::InodeHit); | ||
| event.set_host_path(host_path); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| """ | ||
| Test that verifies inode tracking for newly created files. | ||
|
|
||
| Expected behavior: | ||
| 1. File created in monitored directory | ||
| 2. BPF adds inode to kernel map (if parent is monitored) | ||
| 3. Creation event has non-zero inode | ||
| 4. Subsequent events on that file should also have the inode populated | ||
| """ | ||
|
|
||
| import os | ||
| from tempfile import NamedTemporaryFile | ||
|
|
||
| import pytest | ||
| import yaml | ||
|
|
||
| from event import Event, EventType, Process | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def fact_config(monitored_dir, logs_dir): | ||
| """ | ||
| Config that includes both the directory and its contents. | ||
| This ensures the parent directory inode is tracked. | ||
| """ | ||
| cwd = os.getcwd() | ||
| config = { | ||
| 'paths': [f'{monitored_dir}/**', '/mounted/**', '/container-dir/**'], | ||
|
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 am not sure what globbing to use here. |
||
| 'grpc': { | ||
| 'url': 'http://127.0.0.1:9999', | ||
| }, | ||
| 'endpoint': { | ||
| 'address': '127.0.0.1:9000', | ||
| 'expose_metrics': True, | ||
| 'health_check': True, | ||
| }, | ||
| 'json': True, | ||
| } | ||
| config_file = NamedTemporaryFile( | ||
| prefix='fact-config-', suffix='.yml', dir=cwd, mode='w') | ||
| yaml.dump(config, config_file) | ||
|
|
||
| yield config, config_file.name | ||
| with open(os.path.join(logs_dir, 'fact.yml'), 'w') as f: | ||
| with open(config_file.name, 'r') as r: | ||
| f.write(r.read()) | ||
| config_file.close() | ||
|
|
||
|
|
||
| def test_inode_tracking_on_creation(monitored_dir, test_file, server): | ||
| """ | ||
| Test that when a file is created in a monitored directory, | ||
| its inode is added to the tracking map. | ||
|
|
||
| The test_file fixture ensures the directory exists and has content | ||
| when fact starts, so the parent directory inode gets tracked. | ||
| """ | ||
| # Create a new file | ||
| fut = os.path.join(monitored_dir, 'new_file.txt') | ||
| with open(fut, 'w') as f: | ||
| f.write('initial content') | ||
|
|
||
| # Wait for creation event | ||
| process = Process.from_proc() | ||
| creation_event = Event(process=process, event_type=EventType.CREATION, | ||
| file=fut, host_path='') | ||
|
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. Should |
||
|
|
||
| server.wait_events([creation_event]) | ||
|
|
||
| # Now modify the file - the inode should be tracked from creation | ||
| with open(fut, 'a') as f: | ||
| f.write('appended content') | ||
|
|
||
| # This open event should have host_path populated because the inode | ||
| # was added to the map during creation | ||
| open_event = Event(process=process, event_type=EventType.OPEN, | ||
| file=fut, host_path=fut) | ||
|
|
||
| server.wait_events([open_event]) | ||
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 tried to get the key for the parent using
inode_to_key, but got a verifier error. That function is largely copied here. It is not completely copied, because doing so resulted in a verifier error. This will not work in all cases.